diff --git a/settings.gradle-demo b/settings.gradle-demo index cf7adb44..f2b50542 100644 --- a/settings.gradle-demo +++ b/settings.gradle-demo @@ -69,3 +69,7 @@ // Positions 项目编译设置 //include ':positions' //rootProject.name = "positions" + +// WinBoLL 项目编译设置 +//include ':winboll' +//rootProject.name = "winboll" diff --git a/winboll/build.gradle b/winboll/build.gradle new file mode 100644 index 00000000..b18c367b --- /dev/null +++ b/winboll/build.gradle @@ -0,0 +1,78 @@ +apply plugin: 'com.android.application' +apply from: '../.winboll/winboll_app_build.gradle' +apply from: '../.winboll/winboll_lint_build.gradle' + +def genVersionName(def versionName){ + // 检查编译标志位配置 + assert (winbollBuildProps['stageCount'] != null) + assert (winbollBuildProps['baseVersion'] != null) + // 保存基础版本号 + winbollBuildProps.setProperty("baseVersion", "${versionName}"); + //保存编译标志配置 + FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile) + winbollBuildProps.store(fos, "${winbollBuildPropsDesc}"); + fos.close(); + + // 返回编译版本号 + return "${versionName}." + winbollBuildProps['stageCount'] +} + +android { + compileSdkVersion 32 + buildToolsVersion "32.0.0" + + defaultConfig { + applicationId "cc.winboll.studio.winboll" + minSdkVersion 24 + targetSdkVersion 30 + versionCode 1 + // versionName 更新后需要手动设置 + // .winboll/winbollBuildProps.properties 文件的 stageCount=0 + // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" + versionName "15.0" + if(true) { + versionName = genVersionName("${versionName}") + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + api fileTree(dir: 'libs', include: ['*.jar']) + + api 'com.google.code.gson:gson:2.10.1' + + // 下拉控件 + api 'com.baoyz.pullrefreshlayout:library:1.2.0' + + // SSH + api 'com.jcraft:jsch:0.1.55' + // Html 解析 + api 'org.jsoup:jsoup:1.13.1' + // 二维码类库 + api 'com.google.zxing:core:3.4.1' + api 'com.journeyapps:zxing-android-embedded:3.6.0' + // 应用介绍页类库 + api 'io.github.medyo:android-about-page:2.0.0' + // 吐司类库 + api 'com.github.getActivity:ToastUtils:10.5' + // 网络连接类库 + api 'com.squareup.okhttp3:okhttp:4.4.1' + // AndroidX 类库 + api 'androidx.appcompat:appcompat:1.1.0' + api 'com.google.android.material:material:1.4.0' + //api 'androidx.viewpager:viewpager:1.0.0' + //api 'androidx.vectordrawable:vectordrawable:1.1.0' + //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' + //api 'androidx.fragment:fragment:1.1.0' + + api 'cc.winboll.studio:libaes:15.10.2' + api 'cc.winboll.studio:libapputils:15.10.2' + api 'cc.winboll.studio:libappbase:15.10.9' +} diff --git a/winboll/build.properties b/winboll/build.properties new file mode 100644 index 00000000..894f073d --- /dev/null +++ b/winboll/build.properties @@ -0,0 +1,8 @@ +#Created by .winboll/winboll_app_build.gradle +#Thu Nov 27 01:06:01 GMT 2025 +stageCount=3 +libraryProject= +baseVersion=15.0 +publishVersion=15.0.2 +buildCount=15 +baseBetaVersion=15.0.3 diff --git a/winboll/proguard-rules.pro b/winboll/proguard-rules.pro new file mode 100644 index 00000000..64b4a059 --- /dev/null +++ b/winboll/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/winboll/src/beta/AndroidManifest.xml b/winboll/src/beta/AndroidManifest.xml new file mode 100644 index 00000000..be352251 --- /dev/null +++ b/winboll/src/beta/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/winboll/src/beta/res/values-zh/strings.xml b/winboll/src/beta/res/values-zh/strings.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/winboll/src/beta/res/values-zh/strings.xml @@ -0,0 +1,3 @@ + + + diff --git a/winboll/src/beta/res/values/strings.xml b/winboll/src/beta/res/values/strings.xml new file mode 100644 index 00000000..42cbd7a7 --- /dev/null +++ b/winboll/src/beta/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + WinBoLL+ + 筋斗云★ + 金抖云☆ + + diff --git a/winboll/src/beta/res/xml/shortcutsmaincn1.xml b/winboll/src/beta/res/xml/shortcutsmaincn1.xml new file mode 100644 index 00000000..6b6dc9a0 --- /dev/null +++ b/winboll/src/beta/res/xml/shortcutsmaincn1.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/winboll/src/beta/res/xml/shortcutsmaincn2.xml b/winboll/src/beta/res/xml/shortcutsmaincn2.xml new file mode 100644 index 00000000..2dbb8330 --- /dev/null +++ b/winboll/src/beta/res/xml/shortcutsmaincn2.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/winboll/src/beta/res/xml/shortcutsmainen1.xml b/winboll/src/beta/res/xml/shortcutsmainen1.xml new file mode 100644 index 00000000..4684c41b --- /dev/null +++ b/winboll/src/beta/res/xml/shortcutsmainen1.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml new file mode 100644 index 00000000..163eb03b --- /dev/null +++ b/winboll/src/main/AndroidManifest.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/App.java b/winboll/src/main/java/cc/winboll/studio/winboll/App.java new file mode 100644 index 00000000..1c7ecee3 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/App.java @@ -0,0 +1,357 @@ +package cc.winboll.studio.winboll; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.res.Resources; +import android.graphics.Typeface; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libappbase.GlobalApplication; +import com.hjq.toast.ToastUtils; +import com.hjq.toast.style.WhiteToastStyle; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +public class App extends GlobalApplication { + + public static final String TAG = "App"; + + public static final String COMPONENT_EN1 = "cc.winboll.studio.winboll.MainActivityEN1"; + public static final String COMPONENT_CN1 = "cc.winboll.studio.winboll.MainActivityCN1"; + public static final String COMPONENT_CN2 = "cc.winboll.studio.winboll.MainActivityCN2"; + public static final String ACTION_SWITCHTO_EN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1"; + public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1"; + public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2"; + + private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); + + @Override + public void onCreate() { + super.onCreate(); + + WinBoLLActivityManager.init(this); + + // 初始化 Toast 框架 + ToastUtils.init(this); + // 设置 Toast 布局样式 + //ToastUtils.setView(R.layout.view_toast); + ToastUtils.setStyle(new WhiteToastStyle()); + ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); + + //CrashHandler.getInstance().registerGlobal(this); + //CrashHandler.getInstance().registerPart(this); + } + + public static void write(InputStream input, OutputStream output) throws IOException { + byte[] buf = new byte[1024 * 8]; + int len; + while ((len = input.read(buf)) != -1) { + output.write(buf, 0, len); + } + } + + public static void write(File file, byte[] data) throws IOException { + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) parent.mkdirs(); + + ByteArrayInputStream input = new ByteArrayInputStream(data); + FileOutputStream output = new FileOutputStream(file); + try { + write(input, output); + } finally { + closeIO(input, output); + } + } + + public static String toString(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + write(input, output); + try { + return output.toString("UTF-8"); + } finally { + closeIO(input, output); + } + } + + public static void closeIO(Closeable... closeables) { + for (Closeable closeable : closeables) { + try { + if (closeable != null) closeable.close(); + } catch (IOException ignored) {} + } + } + + public static class CrashHandler { + + public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); + + private static CrashHandler sInstance; + + private PartCrashHandler mPartCrashHandler; + + public static CrashHandler getInstance() { + if (sInstance == null) { + sInstance = new CrashHandler(); + } + return sInstance; + } + + public void registerGlobal(Context context) { + registerGlobal(context, null); + } + + public void registerGlobal(Context context, String crashDir) { + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir)); + } + + public void unregister() { + Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER); + } + + public void registerPart(Context context) { + unregisterPart(context); + mPartCrashHandler = new PartCrashHandler(context.getApplicationContext()); + MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler); + } + + public void unregisterPart(Context context) { + if (mPartCrashHandler != null) { + mPartCrashHandler.isRunning.set(false); + mPartCrashHandler = null; + } + } + + private static class PartCrashHandler implements Runnable { + + private final Context mContext; + + public AtomicBoolean isRunning = new AtomicBoolean(true); + + public PartCrashHandler(Context context) { + this.mContext = context; + } + + @Override + public void run() { + while (isRunning.get()) { + try { + Looper.loop(); + } catch (final Throwable e) { + e.printStackTrace(); + if (isRunning.get()) { + MAIN_HANDLER.post(new Runnable(){ + + @Override + public void run() { + Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show(); + } + }); + } else { + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else { + throw new RuntimeException(e); + } + } + } + } + } + } + + private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler { + + private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss"); + + private final Context mContext; + + private final File mCrashDir; + + public UncaughtExceptionHandlerImpl(Context context, String crashDir) { + this.mContext = context; + this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + try { + + String log = buildLog(throwable); + writeLog(log); + + try { + Intent intent = new Intent(mContext, CrashActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intent.EXTRA_TEXT, log); + mContext.startActivity(intent); + } catch (Throwable e) { + e.printStackTrace(); + writeLog(e.toString()); + } + + throwable.printStackTrace(); + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(0); + + } catch (Throwable e) { + if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); + } + } + + private String buildLog(Throwable throwable) { + String time = DATE_FORMAT.format(new Date()); + + String versionName = "unknown"; + long versionCode = 0; + try { + PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0); + versionName = packageInfo.versionName; + versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode; + } catch (Throwable ignored) {} + + LinkedHashMap head = new LinkedHashMap(); + head.put("Time Of Crash", time); + head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL)); + head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT)); + head.put("App Version", String.format("%s (%d)", versionName, versionCode)); + head.put("Kernel", getKernel()); + head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown"); + head.put("Fingerprint", Build.FINGERPRINT); + + StringBuilder builder = new StringBuilder(); + + for (String key : head.keySet()) { + if (builder.length() != 0) builder.append("\n"); + builder.append(key); + builder.append(" : "); + builder.append(head.get(key)); + } + + builder.append("\n\n"); + builder.append(Log.getStackTraceString(throwable)); + + return builder.toString(); + } + + private void writeLog(String log) { + String time = DATE_FORMAT.format(new Date()); + File file = new File(mCrashDir, "crash_" + time + ".txt"); + try { + write(file, log.getBytes("UTF-8")); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private static String getKernel() { + try { + return App.toString(new FileInputStream("/proc/version")).trim(); + } catch (Throwable e) { + return e.getMessage(); + } + } + } + } + + public static final class CrashActivity extends Activity { + + private String mLog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(android.R.style.Theme_DeviceDefault); + setTitle("App Crash"); + + mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT); + + ScrollView contentView = new ScrollView(this); + contentView.setFillViewport(true); + + HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this); + + TextView textView = new TextView(this); + int padding = dp2px(16); + textView.setPadding(padding, padding, padding, padding); + textView.setText(mLog); + textView.setTextIsSelectable(true); + textView.setTypeface(Typeface.DEFAULT); + textView.setLinksClickable(true); + + horizontalScrollView.addView(textView); + contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + setContentView(contentView); + } + + private void restart() { + Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + finish(); + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(0); + } + + private static int dp2px(float dpValue) { + final float scale = Resources.getSystem().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, android.R.id.copy, 0, android.R.string.copy) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.copy: + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog)); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + restart(); + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/AssistantService.java b/winboll/src/main/java/cc/winboll/studio/winboll/AssistantService.java new file mode 100644 index 00000000..9fa6d1ef --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/AssistantService.java @@ -0,0 +1,96 @@ +package cc.winboll.studio.winboll; + +/** + * @Author ZhanGSKen + * @Date 2025/03/28 19:12:12 + * @Describe 应用主要服务组件类守护进程服务组件类 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import cc.winboll.studio.libaes.models.WinBoLLClientServiceBean; +import cc.winboll.studio.libapputils.utils.ServiceUtils; + +public class AssistantService extends Service { + + public final static String TAG = "AssistantService"; + + WinBoLLClientServiceBean mWinBoLLServiceBean; + MyServiceConnection mMyServiceConnection; + volatile boolean mIsServiceRunning; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + // 设置运行参数 + mIsServiceRunning = false; + run(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + run(); + return START_STICKY; + } + + @Override + public void onDestroy() { + mIsServiceRunning = false; + super.onDestroy(); + } + + // + // 运行服务内容 + // + void run() { + mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this); + if (mWinBoLLServiceBean.isEnable()) { + if (mIsServiceRunning == false) { + // 设置运行状态 + mIsServiceRunning = true; + // 唤醒和绑定主进程 + wakeupAndBindMain(); + } + } + } + + // + // 唤醒和绑定主进程 + // + void wakeupAndBindMain() { + if (ServiceUtils.isServiceAlive(getApplicationContext(), WinBoLLClientService.class.getName()) == false) { + startForegroundService(new Intent(AssistantService.this, WinBoLLClientService.class)); + } + + bindService(new Intent(AssistantService.this, WinBoLLClientService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // + // 主进程与守护进程连接时需要用到此类 + // + class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(AssistantService.this); + if (mWinBoLLServiceBean.isEnable()) { + wakeupAndBindMain(); + } + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/CustomToolbar.java b/winboll/src/main/java/cc/winboll/studio/winboll/CustomToolbar.java new file mode 100644 index 00000000..d9d255ba --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/CustomToolbar.java @@ -0,0 +1,53 @@ +package cc.winboll.studio.winboll; + +/** + * @Author ZhanGSKen + * @Date 2025/05/22 13:08 + */ +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + +public class CustomToolbar extends Toolbar { + + private View viewMain; + + public CustomToolbar(Context context) { + this(context, null); + } + + public CustomToolbar(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context, attrs); + } + + private void initView(Context context, AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar); + + // 获取属性值 + String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle); + int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE); + int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE); + + // 加载布局 + viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true); + + // 应用属性值 + TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title); + toolbarTitleTextView.setText(toolbarTitle); + toolbarTitleTextView.setTextColor(toolbarTitleColor); + viewMain.setBackgroundColor(toolbarBackgroundColor); + + // 释放 TypedArray + typedArray.recycle(); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/EWUIStatusIconDrawable.java b/winboll/src/main/java/cc/winboll/studio/winboll/EWUIStatusIconDrawable.java new file mode 100644 index 00000000..20a70d85 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/EWUIStatusIconDrawable.java @@ -0,0 +1,35 @@ +package cc.winboll.studio.winboll; + +/** + * @Author ZhanGSKen + * @Date 2025/03/28 19:11:27 + * @Describe WinBoLL UI 状态图标枚举 + */ +import cc.winboll.studio.libaes.R; + +public enum EWUIStatusIconDrawable { + NORMAL(0), + NEWS(1) + ; + + static final String TAG = "WUIStatusIconDrawable"; + + static String[] _mlistCNName = { "正常", "新的消息" }; + + private int value = 0; + private EWUIStatusIconDrawable(int value) { //必须是private的,否则编译错误 + this.value = value; + } + + public static int getIconDrawableId(EWUIStatusIconDrawable drawableId) { + int res; + switch(drawableId){ + case NEWS : + res = R.drawable.ic_winbollbeta; + break; + default : + res = R.drawable.ic_winboll; + } + return res; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java new file mode 100644 index 00000000..8ad34fc8 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java @@ -0,0 +1,122 @@ +package cc.winboll.studio.winboll; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Toast; +import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity; +import cc.winboll.studio.libaes.dialogs.LocalFileSelectDialog; +import cc.winboll.studio.libaes.dialogs.StoragePathDialog; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.models.DrawerMenuBean; +import cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity; +import cc.winboll.studio.libaes.unittests.TestAButtonFragment; +import cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity; +import cc.winboll.studio.libaes.unittests.TestAToolbarActivity; +import cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity; +import cc.winboll.studio.libaes.unittests.TestViewPageFragment; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.fragments.MainFragment; +import com.a4455jkjh.colorpicker.ColorPickerDialog; +import java.util.ArrayList; +import cc.winboll.studio.winboll.activities.AboutActivity; + +public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActivity { + + + public static final String TAG = "MainActivity"; + + MainFragment mMainFragment; + + @Override + public Activity getActivity() { + return null; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (mMainFragment == null) { + mMainFragment = new MainFragment(); + addFragment(mMainFragment); + } + showFragment(mMainFragment); + } + + @Override + public void initDrawerMenuItemList(ArrayList listDrawerMenu) { + super.initDrawerMenuItemList(listDrawerMenu); + LogUtils.d(TAG, "initDrawerMenuItemList"); + //listDrawerMenu.clear(); + // 添加抽屉菜单项 + listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG)); + listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG)); + notifyDrawerMenuDataChanged(); + } + + @Override + public void reinitDrawerMenuItemList(ArrayList listDrawerMenu) { + super.reinitDrawerMenuItemList(listDrawerMenu); + LogUtils.d(TAG, "reinitDrawerMenuItemList"); + //listDrawerMenu.clear(); + // 添加抽屉菜单项 + listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG)); + listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG)); + notifyDrawerMenuDataChanged(); + } + + @Override + public DrawerFragmentActivity.ActivityType initActivityType() { + return DrawerFragmentActivity.ActivityType.Main; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar_main, menu); + if(App.isDebuging()) { + getMenuInflater().inflate(cc.winboll.studio.libapputils.R.menu.toolbar_studio_debug, menu); + } + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + super.onItemClick(parent, view, position, id); + switch (position) { + case 0 : { + if (mMainFragment == null) { + mMainFragment = new MainFragment(); + addFragment(mMainFragment); + } + showFragment(mMainFragment); + break; + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int nItemId = item.getItemId(); + if (item.getItemId() == R.id.item_log) { + WinBoLLActivityManager.getInstance().startLogActivity(getApplicationContext()); + } else if (nItemId == R.id.item_about) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), AboutActivity.class); + return true; + } + + return super.onOptionsItemSelected(item); + } + + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java b/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java new file mode 100644 index 00000000..f17f2341 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java @@ -0,0 +1,80 @@ +package cc.winboll.studio.winboll; + +/** + * @Author ZhanGSKen + * @Date 2025/02/13 19:30:10 + */ +import android.content.Context; +import cc.winboll.studio.winboll.R; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import cc.winboll.studio.winboll.models.MainServiceBean; +import cc.winboll.studio.winboll.services.MainService; + +public class MyTileService extends TileService { + public static final String TAG = "MyTileService"; + + volatile static MyTileService _MyTileService; + + @Override + public void onStartListening() { + super.onStartListening(); + _MyTileService = this; + Tile tile = getQsTile(); + MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (bean != null && bean.isEnable()) { + //MainService.startMainService(context); + tile.setState(Tile.STATE_ACTIVE); + tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud)); + } else { + //MainService.stopMainService(context); + tile.setState(Tile.STATE_INACTIVE); + tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); + } + tile.updateTile(); +// Tile tile = getQsTile(); +// tile.setState(Tile.STATE_INACTIVE); +// tile.setLabel(getString(R.string.tileservice_name)); +// tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); +// tile.updateTile(); + + } + + @Override + public void onClick() { + super.onClick(); + Tile tile = getQsTile(); + MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (bean == null) { + bean = new MainServiceBean(); + } + + if (tile.getState() == Tile.STATE_ACTIVE) { + bean.setIsEnable(false); + MainServiceBean.saveBean(this, bean); + MainService.stopMainService(this); + } else if (tile.getState() == Tile.STATE_INACTIVE) { + bean.setIsEnable(true); + MainServiceBean.saveBean(this, bean); + MainService.startMainService(this); + } + updateServiceIconStatus(this); + } + + public static void updateServiceIconStatus(Context context) { + if (_MyTileService == null) { + return; + } + + Tile tile = _MyTileService.getQsTile(); + MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class); + if (bean != null && bean.isEnable()) { + tile.setState(Tile.STATE_ACTIVE); + tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud)); + } else { + tile.setState(Tile.STATE_INACTIVE); + tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline)); + } + tile.updateTile(); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLL.java b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLL.java new file mode 100644 index 00000000..cbdff704 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLL.java @@ -0,0 +1,40 @@ +package cc.winboll.studio.winboll; + +/** + * @Author ZhanGSKen + * @Date 2025/05/10 10:13 + * @Describe WinBoLL 系列应用通用管理类 + */ +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.models.WinBoLLModel; + +public class WinBoLL { + + public static final String TAG = "WinBoLL"; + + public static final String ACTION_BIND = WinBoLL.class.getName() + ".ACTION_BIND"; + public static final String EXTRA_WINBOLLMODEL = "EXTRA_WINBOLLMODEL"; + + public static void bindToAPPBase(Context context, String appMainService) { + LogUtils.d(TAG, "bindToAPPBase(...)"); + String toPackage = "cc.winboll.studio.appbase"; + startBind(context, toPackage, appMainService); + } + + public static void bindToAPPBaseBeta(Context context, String appMainService) { + LogUtils.d(TAG, "bindToAPPBaseBeta(...)"); + String toPackage = "cc.winboll.studio.appbase.beta"; + startBind(context, toPackage, appMainService); + } + + static void startBind(Context context, String toPackage, String appMainService) { + Intent intent = new Intent(ACTION_BIND); + intent.putExtra(EXTRA_WINBOLLMODEL, (new WinBoLLModel(toPackage, appMainService)).toString()); + intent.setPackage(toPackage); + LogUtils.d(TAG, String.format("ACTION_BIND :\nTo Package : %s\nAPP Main Service : %s", toPackage, appMainService)); + context.sendBroadcast(intent); + } + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLClientService.java b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLClientService.java new file mode 100644 index 00000000..c9d29deb --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLClientService.java @@ -0,0 +1,21 @@ +package cc.winboll.studio.winboll; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * @Author ZhanGSKen + * @Date 2025/05/03 19:28 + */ +public class WinBoLLClientService extends Service { + + public static final String TAG = "WinBoLLClientService"; + + @Override + public IBinder onBind(Intent intent) { + + return null; + } + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLServiceStatusView.java b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLServiceStatusView.java new file mode 100644 index 00000000..e939b7e9 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/WinBoLLServiceStatusView.java @@ -0,0 +1,38 @@ +package cc.winboll.studio.winboll; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +/** + * @Author ZhanGSKen + * @Date 2025/05/03 19:14 + */ +public class WinBoLLServiceStatusView extends LinearLayout { + + public static final String TAG = "WinBoLLServiceStatusView"; + + public WinBoLLServiceStatusView(Context context) { + super(context); + } + + public WinBoLLServiceStatusView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + + void setServerHost(String szWinBoLLServerHost) { + + } + + void setAuthInfo(String szDevUserName, String szDevUserPassword) { + + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/AboutActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/AboutActivity.java new file mode 100644 index 00000000..eea24c93 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/AboutActivity.java @@ -0,0 +1,95 @@ +package cc.winboll.studio.winboll.activities; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/29 13:30 + * @Describe 应用介绍窗口 + */ +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.models.APPInfo; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libaes.views.AboutView; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.libaes.BuildConfig; + +public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "AboutActivity"; + + Context mContext; + Toolbar mToolbar; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + setContentView(R.layout.activity_about); + + mToolbar = findViewById(R.id.toolbar); + setSupportActionBar(mToolbar); + mToolbar.setSubtitle(TAG); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + AboutView aboutView = CreateAboutView(); + // 在 Activity 的 onCreate 或其他生命周期方法中调用 +// LinearLayout layout = new LinearLayout(this); +// layout.setOrientation(LinearLayout.VERTICAL); +// // 创建布局参数(宽度和高度) +// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( +// ViewGroup.LayoutParams.MATCH_PARENT, +// ViewGroup.LayoutParams.MATCH_PARENT +// ); +// addContentView(aboutView, params); + + LinearLayout layout = findViewById(R.id.aboutviewroot_ll); + // 创建布局参数(宽度和高度) + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ); + layout.addView(aboutView, params); + + WinBoLLActivityManager.getInstance().add(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + WinBoLLActivityManager.getInstance().registeRemove(this); + } + + public AboutView CreateAboutView() { + String szBranchName = "winboll"; + APPInfo appInfo = new APPInfo(); + appInfo.setAppName("WinBoLL"); + appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); + appInfo.setAppDescription("WinBoLL Description"); + appInfo.setAppGitName("APPBase"); + appInfo.setAppGitOwner("Studio"); + appInfo.setAppGitAPPBranch(szBranchName); + appInfo.setAppGitAPPSubProjectFolder(szBranchName); + appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1"); + appInfo.setAppAPKName("WinBoLL"); + appInfo.setAppAPKFolderName("WinBoLL"); + //appInfo.setIsAddDebugTools(false); + appInfo.setIsAddDebugTools(BuildConfig.DEBUG); + return new AboutView(mContext, appInfo); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java new file mode 100644 index 00000000..70977118 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java @@ -0,0 +1,150 @@ +package cc.winboll.studio.winboll.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.RadioButton; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libappbase.BuildConfig; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.LogView; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.models.UserInfoModel; +import cc.winboll.studio.winboll.utils.RSAUtils; +import cc.winboll.studio.winboll.utils.YunUtils; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * @Author ZhanGSKen + * @Date 2025/06/04 13:29 + * @Describe 用户登录框 + */ +public class LogonActivity extends Activity implements IWinBoLLActivity { + + public static final String TAG = "LogonActivity"; + + public static final String DEBUG_HOST = "http://10.8.0.250:456"; + public static final String YUN_HOST = "https://yun.winboll.cc"; + + + String mHost = ""; + RadioButton mrbYunHost; + RadioButton mrbDebugHost; + LogView mLogView; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_logon); + mLogView = findViewById(R.id.logview); + mLogView.start(); + + mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST; + if (BuildConfig.DEBUG) { + mrbYunHost = findViewById(R.id.rb_yunhost); + mrbDebugHost = findViewById(R.id.rb_debughost); + mrbYunHost.setChecked(!BuildConfig.DEBUG); + mrbDebugHost.setChecked(BuildConfig.DEBUG); + } else { + findViewById(R.id.ll_hostbar).setVisibility(View.GONE); + } + } + + public void onSwitchHost(View view) { + if (view.getId() == R.id.rb_yunhost) { + mrbDebugHost.setChecked(false); + mHost = YUN_HOST; + } else if (view.getId() == R.id.rb_debughost) { + mrbYunHost.setChecked(false); + mHost = DEBUG_HOST; + } + } + + @Override + protected void onResume() { + super.onResume(); + mLogView.start(); + } + + public void onTestLogin(View view) { + LogUtils.d(TAG, "onTestLogin"); + final YunUtils yunUtils = YunUtils.getInstance(this); + + UserInfoModel userInfoModel = new UserInfoModel(); + userInfoModel.setUsername("jian"); + userInfoModel.setPassword("kkiio"); + userInfoModel.setToken("aaa111"); + yunUtils.login(mHost, userInfoModel); + } + + public void onTestRSA(View view) { + LogUtils.d(TAG, "onTestRSA"); + RSAUtils utils = RSAUtils.getInstance(this); + + try { + // 测试 1:首次生成密钥对 + LogUtils.d(TAG, "==== 首次生成密钥对 ===="); + if (utils.keysExist()) { + LogUtils.d(TAG, "密钥对已生成"); + } else { + utils.generateAndSaveKeys(); + LogUtils.d(TAG, "密钥对生成成功。"); + } + + // 测试 2:获取密钥对(自动读取已生成的文件) + KeyPair keyPair = utils.getOrGenerateKeys(); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + // 打印密钥信息 + LogUtils.d(TAG, "\n==== 密钥信息 ===="); + LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm()); + LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节"); + LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm()); + LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节"); + + // 测试 3:重复调用时检查是否复用文件 + LogUtils.d(TAG, "\n==== 二次调用 ===="); + KeyPair reusedPair = utils.getOrGenerateKeys(); + LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用) + LogUtils.d(TAG, "操作完成"); + + String testMessage = "Hello, RSA Encryption!"; + + // 1. 获取或生成密钥对 + PublicKey publicKeyReused = reusedPair.getPublic(); + PrivateKey privateKeyReused = reusedPair.getPrivate(); + + // 2. 公钥加密 + byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused); + LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length); + + // 3. 私钥解密 + String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused); + LogUtils.d(TAG, "解密结果: " + decryptedMessage); + + // 4. 验证解密是否成功 + if (testMessage.equals(decryptedMessage)) { + LogUtils.d(TAG, "加密解密测试通过!"); + } else { + LogUtils.d(TAG, "测试失败:内容不一致"); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java new file mode 100644 index 00000000..c4a0bc65 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java @@ -0,0 +1,77 @@ +package cc.winboll.studio.winboll.activities; + +/** + * @Author ZhanGSKen + * @Date 2025/03/25 11:46:40 + * @Describe 测试窗口2 + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toolbar; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.winboll.R; + +public class New2Activity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "New2Activity"; + + Toolbar mToolbar; + //LogView mLogView; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new2); + +// mLogView = findViewById(R.id.logview); +// mLogView.start(); + mToolbar = findViewById(R.id.toolbar); + setActionBar(mToolbar); + + } + + @Override + protected void onResume() { + super.onResume(); + //mLogView.start(); + } + + public void onCloseThisActivity(View view) { + WinBoLLActivityManager.getInstance().finish(this); + } + + public void onCloseAllActivity(View view) { + WinBoLLActivityManager.getInstance().finishAll(); + } + + public void onNewActivity(View view) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //getMenuInflater().inflate(R.menu.toolbar_main, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 + return super.onOptionsItemSelected(item); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java new file mode 100644 index 00000000..f20d4ece --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java @@ -0,0 +1,76 @@ +package cc.winboll.studio.winboll.activities; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/03/25 05:04:22 + * @Describe + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toolbar; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.winboll.R; + +public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "NewActivity"; + + Toolbar mToolbar; + //LogView mLogView; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new); +// mLogView = findViewById(R.id.logview); +// mLogView.start(); + mToolbar = findViewById(R.id.toolbar); + setActionBar(mToolbar); + + } + + @Override + protected void onResume() { + super.onResume(); + //mLogView.start(); + } + + public void onCloseThisActivity(View view) { + WinBoLLActivityManager.getInstance().finish(this); + } + + public void onCloseAllActivity(View view) { + WinBoLLActivityManager.getInstance().finishAll(); + } + + public void onNew2Activity(View view) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, New2Activity.class); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //getMenuInflater().inflate(R.menu.toolbar_main, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 + return super.onOptionsItemSelected(item); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/ShortcutActionActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/ShortcutActionActivity.java new file mode 100644 index 00000000..d906af3c --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/ShortcutActionActivity.java @@ -0,0 +1,49 @@ +package cc.winboll.studio.winboll.activities; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.winboll.App; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.utils.APPPlusUtils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/27 09:00 + * @Describe 应用快捷方式活动类 + */ +public class ShortcutActionActivity extends Activity { + + public static final String TAG = "ShortcutActionActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 处理应用级别的切换请求 + handleSwitchRequest(); + finish(); + } + + /** + * 处理应用图标快捷菜单的请求 + */ + private void handleSwitchRequest() { + Intent intent = getIntent(); + if (intent != null && "switchto_en1".equals(intent.getDataString())) { + APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1); + ToastUtils.show("切换至" + getString(R.string.app_name) + "图标"); + //moveTaskToBack(true); + } + if (intent != null && "switchto_cn1".equals(intent.getDataString())) { + APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN1); + ToastUtils.show("切换至" + getString(R.string.app_name_cn1) + "图标"); + //moveTaskToBack(true); + } + if (intent != null && "switchto_cn2".equals(intent.getDataString())) { + APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN2); + ToastUtils.show("切换至" + getString(R.string.app_name_cn2) + "图标"); + //moveTaskToBack(true); + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java new file mode 100644 index 00000000..15c5e533 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java @@ -0,0 +1,60 @@ +package cc.winboll.studio.winboll.activities; + +/** + * @Author ZhanGSKen + * @Date 2025/05/10 09:48 + * @Describe WinBoLL 窗口基础类 + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.MenuItem; +import androidx.appcompat.app.AppCompatActivity; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libappbase.LogUtils; + +public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity { + + public static final String TAG = "WinBoLLActivity"; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onResume() { + super.onResume(); + LogUtils.d(TAG, String.format("onResume %s", getTag())); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + /*if (item.getItemId() == R.id.item_log) { + WinBoLLActivityManager.getInstance().startLogActivity(this); + return true; + } else if (item.getItemId() == R.id.item_home) { + startActivity(new Intent(this, MainActivity.class)); + return true; + }*/ + // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 + return super.onOptionsItemSelected(item); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + WinBoLLActivityManager.getInstance().add(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + WinBoLLActivityManager.getInstance().registeRemove(this); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java new file mode 100644 index 00000000..65b2c802 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java @@ -0,0 +1,172 @@ +package cc.winboll.studio.winboll.activities; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libappbase.CrashHandler; +import cc.winboll.studio.libappbase.GlobalApplication; +import cc.winboll.studio.libappbase.GlobalCrashActivity; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.winboll.App; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.services.MainService; +import cc.winboll.studio.winboll.services.TestDemoBindService; +import cc.winboll.studio.winboll.services.TestDemoService; +import cc.winboll.studio.winboll.sos.SOS; +import cc.winboll.studio.winboll.widgets.StatusWidget; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +public class WinBoLLUnitTestActivity extends AppCompatActivity { + + public static final String TAG = "MainActivity"; + + Toolbar mToolbar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ToastUtils.show("onCreate"); + setContentView(R.layout.activity_winbollunittest); + + mToolbar = findViewById(R.id.toolbar); + setSupportActionBar(mToolbar); + + CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1); + cbIsDebugMode.setChecked(GlobalApplication.isDebuging()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //getMenuInflater().inflate(R.menu.toolbar_main, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.item_yun) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, cc.winboll.studio.winboll.activities.YunActivity.class); + } else if(item.getItemId() == R.id.item_logon) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, cc.winboll.studio.winboll.activities.LogonActivity.class); + } + // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 + return super.onOptionsItemSelected(item); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Intent intentAPPWidget = new Intent(this, StatusWidget.class); + intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE); + sendBroadcast(intentAPPWidget); + } + + public void onSwitchDebugMode(View view) { + boolean isDebuging = ((CheckBox)view).isChecked(); + GlobalApplication.setIsDebuging(isDebuging); + GlobalApplication.saveDebugStatus(this); + } + + public void onPreviewGlobalCrashActivity(View view) { + Intent intent = new Intent(this, GlobalCrashActivity.class); + intent.putExtra(CrashHandler.EXTRA_CRASH_INFO, "Demo log..."); + startActivity(intent); + } + + public void onStartCenter(View view) { + MainService.startMainService(this); + } + + public void onStopCenter(View view) { + MainService.stopMainService(this); + } + + public void onTestStopMainServiceWithoutSettingEnable(View view) { + LogUtils.d(TAG, "onTestStopMainServiceWithoutSettingEnable"); + stopService(new Intent(this, MainService.class)); + } + + public void onTestUseComponentStartService(View view) { + LogUtils.d(TAG, "onTestUseComponentStartService"); + + // 目标服务的包名和类名 + String packageName = this.getPackageName(); + String serviceClassName = TestDemoService.class.getName(); + + // 构建Intent + Intent intentService = new Intent(); + intentService.setComponent(new ComponentName(packageName, serviceClassName)); + + startService(intentService); + } + + public void onTestDemoServiceSOS(View view) { + Intent intent = new Intent(this, TestDemoService.class); + stopService(intent); + if (App.isDebuging()) { + SOS.sosToAppBaseBeta(this, TestDemoService.class.getName()); + } else { + SOS.sosToAppBase(this, TestDemoService.class.getName()); + } + } + + public void onSartTestDemoService(View view) { + Intent intent = new Intent(this, TestDemoService.class); + intent.setAction(TestDemoService.ACTION_ENABLE); + startService(intent); + + } + + public void onStopTestDemoService(View view) { + Intent intent = new Intent(this, TestDemoService.class); + intent.setAction(TestDemoService.ACTION_DISABLE); + startService(intent); + + Intent intentStop = new Intent(this, TestDemoService.class); + stopService(intentStop); + } + + public void onStopTestDemoServiceNoSettings(View view) { + Intent intent = new Intent(this, TestDemoService.class); + stopService(intent); + } + + public void onSartTestDemoBindService(View view) { + Intent intent = new Intent(this, TestDemoBindService.class); + intent.setAction(TestDemoBindService.ACTION_ENABLE); + startService(intent); + + } + + public void onStopTestDemoBindService(View view) { + Intent intent = new Intent(this, TestDemoBindService.class); + intent.setAction(TestDemoBindService.ACTION_DISABLE); + startService(intent); + + Intent intentStop = new Intent(this, TestDemoBindService.class); + stopService(intentStop); + } + + public void onStopTestDemoBindServiceNoSettings(View view) { + Intent intent = new Intent(this, TestDemoBindService.class); + stopService(intent); + } + + public void onTestOpenNewActivity(View view) { + WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class); + } + + @Override + protected void onResume() { + super.onResume(); + } + + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java new file mode 100644 index 00000000..8ab5fb66 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java @@ -0,0 +1,126 @@ +package cc.winboll.studio.winboll.activities; + +/** + * @Author ZhanGSKen + * @Date 2025/06/04 11:06 + * @Describe 云宝云 + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.RadioButton; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libappbase.BuildConfig; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.LogView; +import cc.winboll.studio.winboll.R; +import java.io.IOException; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class YunActivity extends Activity implements IWinBoLLActivity { + + public static final String TAG = "YunActivity"; + + public static final String DEBUG_HOST = "http://10.8.0.250:456"; + public static final String YUN_HOST = "https://yun.winboll.cc"; + + String mHost = ""; + RadioButton mrbYunHost; + RadioButton mrbDebugHost; + LogView mLogView; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_yun); + mLogView = findViewById(R.id.logview); + mLogView.start(); + + mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST; + if (BuildConfig.DEBUG) { + mrbYunHost = findViewById(R.id.rb_yunhost); + mrbDebugHost = findViewById(R.id.rb_debughost); + mrbYunHost.setChecked(!BuildConfig.DEBUG); + mrbDebugHost.setChecked(BuildConfig.DEBUG); + } else { + findViewById(R.id.ll_hostbar).setVisibility(View.GONE); + } + } + + public void onSwitchHost(View view) { + if (view.getId() == R.id.rb_yunhost) { + mrbDebugHost.setChecked(false); + mHost = YUN_HOST; + } else if (view.getId() == R.id.rb_debughost) { + mrbYunHost.setChecked(false); + mHost = DEBUG_HOST; + } + } + + @Override + protected void onResume() { + super.onResume(); + mLogView.start(); + } + + public void onTestYun(View view) { + LogUtils.d(TAG, "onTestYun"); + (new Thread(new Runnable(){ + @Override + public void run() { + testYun(); + } + })).start(); + } + + void testYun() { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(mHost + "/backups/") + .build(); + + Response response = null; + try { + response = client.newCall(request).execute(); + if (response.isSuccessful()) { + String responseBody = ""; + if (response.body() != null) { + responseBody = response.body().string(); + } + + // 正则匹配:任意主机名 -> Test OK(主机名部分匹配非空字符) + boolean isMatch = responseBody.matches(".+? -> Test OK"); + + if (isMatch) { + LogUtils.d(TAG, responseBody); + } else { + LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody); + } + } else { + LogUtils.d(TAG, "请求失败,状态码:" + response.code()); + } + } catch (IOException e) { + LogUtils.d(TAG, "读取响应体失败:" + e.getMessage()); + } catch (Exception e) { + LogUtils.d(TAG, "异常:" + e.getMessage()); + e.printStackTrace(); // Java 7 需显式打印堆栈 + } finally { + // 手动关闭 Response(Java 7 不支持 try-with-resources) + if (response != null && response.body() != null) { + response.body().close(); + } + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/fragments/MainFragment.java b/winboll/src/main/java/cc/winboll/studio/winboll/fragments/MainFragment.java new file mode 100644 index 00000000..a4ab575d --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/fragments/MainFragment.java @@ -0,0 +1,37 @@ +package cc.winboll.studio.winboll.fragments; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/29 13:15 + * @Describe MainFragment + */ +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.fragment.app.Fragment; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.libaes.views.AButton; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import android.widget.Switch; + + +public class MainFragment extends Fragment { + + public static final String TAG = "MainFragment"; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_main, container, false); + Switch swEnablePosition = view.findViewById(R.id.fragmentmainSwitch1); + swEnablePosition.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastUtils.show("Position"); + } + }); + return view; + } + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java b/winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java new file mode 100644 index 00000000..effeb436 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java @@ -0,0 +1,38 @@ +package cc.winboll.studio.winboll.handlers; + +/** + * @Author ZhanGSKen + * @Date 2025/02/14 03:51:40 + */ +import android.os.Handler; +import android.os.Message; +import java.lang.ref.WeakReference; +import cc.winboll.studio.winboll.services.MainService; + +public class MainServiceHandler extends Handler { + public static final String TAG = "MainServiceHandler"; + + public static final int MSG_REMINDTHREAD = 0; + + WeakReference serviceWeakReference; + public MainServiceHandler(MainService service) { + serviceWeakReference = new WeakReference(service); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI + { + // 显示提醒消息 + // + //LogUtils.d(TAG, "显示提醒消息"); + MainService mainService = serviceWeakReference.get(); + if (mainService != null) { + mainService.appenMessage((String)msg.obj); + } + break; + } + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java new file mode 100644 index 00000000..98b64a13 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java @@ -0,0 +1,67 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/02/13 07:06:13 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class MainServiceBean extends BaseBean { + + public static final String TAG = "MainServiceBean"; + + boolean isEnable; + + public MainServiceBean() { + this.isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return MainServiceBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("isEnable").value(isEnable()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("isEnable")) { + setIsEnable(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/ResponseData.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/ResponseData.java new file mode 100644 index 00000000..ed78bdc0 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/ResponseData.java @@ -0,0 +1,53 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/06/05 11:26 + */ + +public class ResponseData { + + public static final String STATUS_SUCCESS = "success"; + public static final String STATUS_ERROR = "error"; + + private String status; + private String message; + private UserInfoModel data; + + public ResponseData() { + this.status = ""; + this.message = ""; + this.data = new UserInfoModel(); + } + + public ResponseData(String status, String message, UserInfoModel data) { + this.status = status; + this.message = message; + this.data = data; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setData(UserInfoModel data) { + this.data = data; + } + + public UserInfoModel getData() { + return data; + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java new file mode 100644 index 00000000..ab680510 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java @@ -0,0 +1,67 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/03/07 12:47:22 + * @Describe TestServiceBean + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class TestDemoBindServiceBean extends BaseBean { + + public static final String TAG = "TestServiceBean"; + + boolean isEnable; + + public TestDemoBindServiceBean() { + this.isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return TestDemoBindServiceBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("isEnable").value(isEnable()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("isEnable")) { + setIsEnable(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java new file mode 100644 index 00000000..5899eca1 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java @@ -0,0 +1,68 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/03/07 12:49:21 + * @Describe TestDemoServiceBean + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class TestDemoServiceBean extends BaseBean { + + public static final String TAG = "TestDemoServiceBean"; + + boolean isEnable; + + public TestDemoServiceBean() { + this.isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return TestDemoServiceBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("isEnable").value(isEnable()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("isEnable")) { + setIsEnable(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/UserInfoModel.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/UserInfoModel.java new file mode 100644 index 00000000..52c4e4c5 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/UserInfoModel.java @@ -0,0 +1,92 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/06/04 19:14 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class UserInfoModel extends BaseBean { + + public static final String TAG = "UserInfoModel"; + + String username; + String password; + String token; + + public UserInfoModel() { + this.username = ""; + this.password = ""; + this.token = ""; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } + + public void setToken(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + + @Override + public String getName() { + return UserInfoModel.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("username").value(getUsername()); + jsonWriter.name("password").value(getPassword()); + jsonWriter.name("token").value(getToken()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("username")) { + setUsername(jsonReader.nextString()); + } else if (name.equals("password")) { + setPassword(jsonReader.nextString()); + } else if (name.equals("token")) { + setToken(jsonReader.nextString()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLModel.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLModel.java new file mode 100644 index 00000000..6b5d65d0 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLModel.java @@ -0,0 +1,92 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/05/10 10:16 + * @Describe WinBoLLModel + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; +import cc.winboll.studio.libappbase.APPModel; + +public class WinBoLLModel extends BaseBean { + + public static final String TAG = "WinBoLLModel"; + + String appPackageName; + String appMainServiveName; + + public WinBoLLModel() { + this.appPackageName = ""; + this.appMainServiveName = ""; + } + + public WinBoLLModel(boolean isDebuging, String appPackageName, String appMainServiveName) { + this.appPackageName = appPackageName; + this.appMainServiveName = appMainServiveName; + } + + public WinBoLLModel(String appPackageName, String appMainServiveName) { + this.appPackageName = appPackageName; + this.appMainServiveName = appMainServiveName; + } + + public void setAppPackageName(String appPackageName) { + this.appPackageName = appPackageName; + } + + public String getAppPackageName() { + return appPackageName; + } + + public void setAppMainServiveName(String appMainServiveName) { + this.appMainServiveName = appMainServiveName; + } + + public String getAppMainServiveName() { + return appMainServiveName; + } + + @Override + public String getName() { + return APPModel.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("appPackageName").value(getAppPackageName()); + jsonWriter.name("appMainServiveName").value(getAppMainServiveName()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("appPackageName")) { + setAppPackageName(jsonReader.nextString()); + } else if (name.equals("appMainServiveName")) { + setAppMainServiveName(jsonReader.nextString()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java new file mode 100644 index 00000000..e7491963 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java @@ -0,0 +1,71 @@ +package cc.winboll.studio.winboll.models; + +/** + * @Author ZhanGSKen + * @Date 2025/05/10 09:36 + * @Describe WinBoLL 应用消息数据模型 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class WinBoLLNewsBean extends BaseBean { + + public static final String TAG = "WinBoLLNewsBean"; + + String message; + + public WinBoLLNewsBean() { + this.message = ""; + } + + public WinBoLLNewsBean(String message) { + this.message = message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public String getName() { + return WinBoLLNewsBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("message").value(getMessage()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("message")) { + setMessage(jsonReader.nextString()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java new file mode 100644 index 00000000..4e3681d2 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java @@ -0,0 +1,36 @@ +package cc.winboll.studio.winboll.receivers; + +/** + * @Author ZhanGSKen + * @Date 2025/03/24 07:11:44 + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.widgets.APPNewsWidget; + +public class APPNewsWidgetClickListener extends BroadcastReceiver { + + public static final String TAG = "APPNewsWidgetClickListener"; + public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE"; + public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + LogUtils.d(TAG, String.format("action %s", action)); + return; + } + if (action.equals(ACTION_PRE)) { + LogUtils.d(TAG, "ACTION_PRE"); + APPNewsWidget.prePage(context); + } else if (action.equals(ACTION_NEXT)) { + LogUtils.d(TAG, "ACTION_NEXT"); + APPNewsWidget.nextPage(context); + } else { + LogUtils.d(TAG, String.format("action %s", action)); + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java new file mode 100644 index 00000000..8fa99b9f --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java @@ -0,0 +1,118 @@ +package cc.winboll.studio.winboll.receivers; + +/** + * @Author ZhanGSKen + * @Date 2025/02/13 06:58:04 + * @Describe 主要广播接收器 + */ +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.libapputils.utils.AppUtils; +import cc.winboll.studio.winboll.WinBoLL; +import cc.winboll.studio.winboll.models.WinBoLLModel; +import cc.winboll.studio.winboll.models.WinBoLLNewsBean; +import cc.winboll.studio.winboll.services.MainService; +import cc.winboll.studio.winboll.sos.SOS; +import cc.winboll.studio.winboll.sos.SOSObject; +import cc.winboll.studio.winboll.widgets.APPNewsWidget; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MainReceiver extends BroadcastReceiver { + + public static final String TAG = "MainReceiver"; + + public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + + WeakReference mwrService; + + public MainReceiver(MainService service) { + mwrService = new WeakReference(service); + } + + @Override + public void onReceive(Context context, Intent intent) { + String szAction = intent.getAction(); + if (szAction.equals(ACTION_BOOT_COMPLETED)) { + ToastUtils.show("ACTION_BOOT_COMPLETED"); + } else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) { + LogUtils.d(TAG, "ACTION_BIND"); + LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName())); + LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction())); + String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL); + LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel)); + if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) { + try { + WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class); + if (bean != null) { + String szAppPackageName = bean.getAppPackageName(); + LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName)); + String szAppMainServiveName = bean.getAppMainServiveName(); + LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName)); + mwrService.get().bindWinBoLLModelConnection(bean); + } + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + } else if (intent.getAction().equals(SOS.ACTION_SOS)) { + LogUtils.d(TAG, "ACTION_SOS"); + String sos = intent.getStringExtra(SOS.EXTRA_OBJECT); + LogUtils.d(TAG, String.format("SOS %s", sos)); + if (sos != null && !sos.equals("")) { + SOSObject bean = SOS.parseSOSObject(sos); + if (bean != null) { + String szObjectPackageName = bean.getObjectPackageName(); + LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName)); + String szObjectServiveName = bean.getObjectServiveName(); + LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName)); + + Intent intentService = new Intent(); + intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName)); + context.startService(intentService); + + String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName); + LogUtils.d(TAG, String.format("appName %s", appName)); + WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName); + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); + String currentTime = sdf.format(new Date()); + StringBuilder sbLine = new StringBuilder(); + sbLine.append("["); + sbLine.append(currentTime); + sbLine.append("] Power to "); + sbLine.append(appName); + appWinBoLLNewsBean.setMessage(sbLine.toString()); + + APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean); + + Intent intentWidget = new Intent(context, APPNewsWidget.class); + intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); + context.sendBroadcast(intentWidget); + } + + + } + } else { + ToastUtils.show(szAction); + } + } + + // 注册 Receiver + // + public void registerAction(MainService service) { + IntentFilter filter=new IntentFilter(); + filter.addAction(ACTION_BOOT_COMPLETED); + filter.addAction(SOS.ACTION_SOS); + filter.addAction(WinBoLL.ACTION_BIND); + //filter.addAction(Intent.ACTION_BATTERY_CHANGED); + service.registerReceiver(this, filter); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java new file mode 100644 index 00000000..1bf9d0e3 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java @@ -0,0 +1,29 @@ +package cc.winboll.studio.winboll.receivers; + +/** + * @Author ZhanGSKen + * @Date 2025/02/13 21:19:09 + * @Describe MyBroadcastReceiver + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.R; + +public class MyBroadcastReceiver extends BroadcastReceiver { + + public static final String TAG = "MyBroadcastReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + if (context.getString(R.string.action_sos).equals(intent.getAction())) { + String message = intent.getStringExtra("message"); + String sosPackage = intent.getStringExtra("sosPackage"); + + // 处理接收到的广播消息 + LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message)); + } + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java new file mode 100644 index 00000000..2fd60768 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java @@ -0,0 +1,136 @@ +package cc.winboll.studio.winboll.services; + +/** + * @Author ZhanGSKen + * @Date 2025/02/14 03:38:31 + * @Describe 守护进程服务 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; +import android.os.Binder; +import cc.winboll.studio.winboll.models.MainServiceBean; + +public class AssistantService extends Service { + + public static final String TAG = "AssistantService"; + + MainServiceBean mMainServiceBean; + MyServiceConnection mMyServiceConnection; + MainService mMainService; + boolean isBound = false; + volatile boolean isThreadAlive = false; + + public synchronized void setIsThreadAlive(boolean isThreadAlive) { + LogUtils.d(TAG, "setIsThreadAlive(...)"); + LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive)); + this.isThreadAlive = isThreadAlive; + } + + public boolean isThreadAlive() { + return isThreadAlive; + } + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + @Override + public void onCreate() { + LogUtils.d(TAG, "onCreate"); + super.onCreate(); + + //mMyBinder = new MyBinder(); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + // 设置运行参数 + setIsThreadAlive(false); + assistantService(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "call onStartCommand(...)"); + assistantService(); + return START_STICKY; + } + + @Override + public void onDestroy() { + //LogUtils.d(TAG, "onDestroy"); + setIsThreadAlive(false); + // 解除绑定 + if (isBound) { + unbindService(mMyServiceConnection); + isBound = false; + } + super.onDestroy(); + } + + // 运行服务内容 + // + void assistantService() { + LogUtils.d(TAG, "assistantService()"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable())); + if (mMainServiceBean.isEnable()) { + LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive())); + if (isThreadAlive() == false) { + // 设置运行状态 + setIsThreadAlive(true); + // 唤醒和绑定主进程 + wakeupAndBindMain(); + } + } + } + + // 唤醒和绑定主进程 + // + void wakeupAndBindMain() { + LogUtils.d(TAG, "wakeupAndBindMain()"); + // 绑定服务的Intent + Intent intent = new Intent(this, MainService.class); + startService(new Intent(this, MainService.class)); + bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); + +// startService(new Intent(this, MainService.class)); +// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // 主进程与守护进程连接时需要用到此类 + // + class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LogUtils.d(TAG, "onServiceConnected(...)"); + MainService.MyBinder binder = (MainService.MyBinder) service; + mMainService = binder.getService(); + isBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.d(TAG, "onServiceDisconnected(...)"); + mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class); + if (mMainServiceBean.isEnable()) { + wakeupAndBindMain(); + } + isBound = false; + mMainService = null; + } + } + + // 用于返回服务实例的Binder + public class MyBinder extends Binder { + AssistantService getService() { + LogUtils.d(TAG, "AssistantService MyBinder getService()"); + return AssistantService.this; + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java new file mode 100644 index 00000000..d2873771 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java @@ -0,0 +1,316 @@ +package cc.winboll.studio.winboll.services; + +/** + * @Author ZhanGSKen + * @Date 2025/02/13 06:56:41 + * @Describe 拨号主服务 + * 参考: + * 进程保活-双进程守护的正确姿势 + * https://blog.csdn.net/sinat_35159441/article/details/75267380 + * Android Service之onStartCommand方法研究 + * https://blog.csdn.net/cyp331203/article/details/38920491 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.MyTileService; +import cc.winboll.studio.winboll.handlers.MainServiceHandler; +import cc.winboll.studio.winboll.models.MainServiceBean; +import cc.winboll.studio.winboll.models.WinBoLLModel; +import cc.winboll.studio.winboll.receivers.MainReceiver; +import cc.winboll.studio.winboll.services.AssistantService; +import cc.winboll.studio.winboll.threads.MainServiceThread; +import cc.winboll.studio.winboll.widgets.APPNewsWidget; +import java.util.ArrayList; + +public class MainService extends Service { + + public static final String TAG = "MainService"; + + public static final int MSG_UPDATE_STATUS = 0; + + static MainService _mControlCenterService; + + volatile boolean isServiceRunning; + + MainServiceBean mMainServiceBean; + MainServiceThread mMainServiceThread; + MainServiceHandler mMainServiceHandler; + MyServiceConnection mMyServiceConnection; + AssistantService mAssistantService; + boolean isBound = false; + MainReceiver mMainReceiver; + ArrayList mAPPModelConnectionList; + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + public MainServiceThread getRemindThread() { + return mMainServiceThread; + } + + @Override + public void onCreate() { + super.onCreate(); + LogUtils.d(TAG, "onCreate()"); + mAPPModelConnectionList = new ArrayList(); + + _mControlCenterService = MainService.this; + isServiceRunning = false; + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + mMainServiceHandler = new MainServiceHandler(this); + + // 运行服务内容 + mainService(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "onStartCommand(...)"); + // 运行服务内容 + mainService(); + return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); + } + + // 运行服务内容 + // + void mainService() { + LogUtils.d(TAG, "mainService()"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (mMainServiceBean.isEnable() && isServiceRunning == false) { + LogUtils.d(TAG, "mainService() start running"); + isServiceRunning = true; + // 唤醒守护进程 + wakeupAndBindAssistant(); + + if (mMainReceiver == null) { + // 注册广播接收器 + mMainReceiver = new MainReceiver(this); + mMainReceiver.registerAction(this); + } + + // 启动小部件 + Intent intentTimeWidget = new Intent(this, APPNewsWidget.class); + intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); + this.sendBroadcast(intentTimeWidget); + + startMainServiceThread(); + + MyTileService.updateServiceIconStatus(this); + + LogUtils.i(TAG, "Main Service Is Start."); + } + } + + // 唤醒和绑定守护进程 + // + void wakeupAndBindAssistant() { + LogUtils.d(TAG, "wakeupAndBindAssistant()"); + + Intent intent = new Intent(this, AssistantService.class); + startService(intent); + // 绑定服务的Intent + bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // 开启提醒铃声线程 + // + public void startMainServiceThread() { + LogUtils.d(TAG, "startMainServiceThread"); + if (mMainServiceThread == null) { + mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); + LogUtils.d(TAG, "new MainServiceThread"); + } else { + if (mMainServiceThread.isExist() == true) { + mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); + LogUtils.d(TAG, "renew MainServiceThread"); + } else { + // 提醒进程正在进行中就更新状态后退出 + LogUtils.d(TAG, "A mMainServiceThread running."); + return; + } + } + mMainServiceThread.start(); + } + + public void stopRemindThread() { + if (mMainServiceThread != null) { + mMainServiceThread.setIsExist(true); + mMainServiceThread = null; + } + } + + @Override + public void onDestroy() { + //LogUtils.d(TAG, "onDestroy"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (mMainServiceBean.isEnable() == false) { + // 设置运行状态 + isServiceRunning = false;// 解除绑定 + if (isBound) { + unbindService(mMyServiceConnection); + isBound = false; + } + // 停止守护进程 + Intent intent = new Intent(this, AssistantService.class); + stopService(intent); + // 停止Receiver + if (mMainReceiver != null) { + unregisterReceiver(mMainReceiver); + mMainReceiver = null; + } + // 停止前台通知栏 + stopForeground(true); + // 停止消息提醒进程 + stopRemindThread(); + + MyTileService.updateServiceIconStatus(this); + + super.onDestroy(); + //LogUtils.d(TAG, "onDestroy done"); + } + } + + public void bindWinBoLLModelConnection(WinBoLLModel bean) { + LogUtils.d(TAG, "bindAPPModelConnection(...)"); + // 清理旧的绑定链接 + for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) { + APPConnection item = mAPPModelConnectionList.get(i); + if (item.isBindToAPP(bean)) { + LogUtils.d(TAG, "Bind Servive exist."); + unbindService(item); + mAPPModelConnectionList.remove(i); + } + } + + // 绑定服务 + APPConnection appConnection = new APPConnection(); + Intent intentService = new Intent(); + intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName())); + bindService(intentService, appConnection, Context.BIND_IMPORTANT); + mAPPModelConnectionList.add(appConnection); + + Intent intentWidget = new Intent(this, APPNewsWidget.class); + intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE); + WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName()); + intentWidget.putExtra("APPSOSBean", appSOSBean.toString()); + sendBroadcast(intentWidget); + } + + public class APPConnection implements ServiceConnection { + + ComponentName mComponentName; + + boolean isBindToAPP(WinBoLLModel bean) { + return mComponentName != null + && mComponentName.getClassName().equals(bean.getAppMainServiveName()) + && mComponentName.getPackageName().equals(bean.getAppPackageName()); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LogUtils.d(TAG, "onServiceConnected(...)"); + mComponentName = name; + LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.d(TAG, "onServiceDisconnected(...)"); + LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); + + // 尝试无参数启动一下服务 + String appPackage = mComponentName.getPackageName(); + LogUtils.d(TAG, String.format("appPackage %s", appPackage)); + String appMainServiceClassName = mComponentName.getClassName(); + LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName)); + + Intent intentService = new Intent(); + intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName)); + startService(intentService); + } + + } + + // 主进程与守护进程连接时需要用到此类 + // + private class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LogUtils.d(TAG, "onServiceConnected(...)"); + AssistantService.MyBinder binder = (AssistantService.MyBinder) service; + mAssistantService = binder.getService(); + isBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.d(TAG, "onServiceDisconnected(...)"); + + if (mMainServiceBean.isEnable()) { + // 唤醒守护进程 + wakeupAndBindAssistant(); + } + isBound = false; + mAssistantService = null; + } + + } + + + // 用于返回服务实例的Binder + public class MyBinder extends Binder { + MainService getService() { + LogUtils.d(TAG, "MainService MyBinder getService()"); + return MainService.this; + } + } + +// // +// // 启动服务 +// // +// public static void startControlCenterService(Context context) { +// Intent intent = new Intent(context, MainService.class); +// context.startForegroundService(intent); +// } +// +// // +// // 停止服务 +// // +// public static void stopControlCenterService(Context context) { +// Intent intent = new Intent(context, MainService.class); +// context.stopService(intent); +// } + + public void appenMessage(String message) { + LogUtils.d(TAG, String.format("Message : %s", message)); + } + + public static void stopMainService(Context context) { + LogUtils.d(TAG, "stopMainService"); + MainServiceBean bean = new MainServiceBean(); + bean.setIsEnable(false); + MainServiceBean.saveBean(context, bean); + context.stopService(new Intent(context, MainService.class)); + } + + public static void startMainService(Context context) { + LogUtils.d(TAG, "startMainService"); + MainServiceBean bean = new MainServiceBean(); + bean.setIsEnable(true); + MainServiceBean.saveBean(context, bean); + context.startService(new Intent(context, MainService.class)); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java new file mode 100644 index 00000000..cb99ad35 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java @@ -0,0 +1,179 @@ +package cc.winboll.studio.winboll.services; + +/** + * @Author ZhanGSKen + * @Date 2025/03/07 12:45:49 + * @Describe 启动时申请绑定到APPBase主服务的服务示例 + */ +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.App; +import cc.winboll.studio.winboll.WinBoLL; +import cc.winboll.studio.winboll.models.TestDemoBindServiceBean; +import cc.winboll.studio.winboll.services.TestDemoBindService; +import cc.winboll.studio.winboll.sos.SOS; + +public class TestDemoBindService extends Service { + + public static final String TAG = "TestDemoBindService"; + + public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE"; + public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE"; + + volatile static TestThread _TestThread; + + volatile static boolean _IsRunning; + + public synchronized static void setIsRunning(boolean isRunning) { + _IsRunning = isRunning; + } + + public static boolean isRunning() { + return _IsRunning; + } + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + public class MyBinder extends Binder { + public TestDemoBindService getService() { + return TestDemoBindService.this; + } + } + + @Override + public void onCreate() { + super.onCreate(); + LogUtils.d(TAG, "onCreate()"); + + run(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "onStartCommand(...)"); + TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); + if (bean == null) { + bean = new TestDemoBindServiceBean(); + } + + if (intent.getAction() != null) { + if (intent.getAction().equals(ACTION_ENABLE)) { + bean.setIsEnable(true); + LogUtils.d(TAG, "setIsEnable(true);"); + TestDemoBindServiceBean.saveBean(this, bean); + } else if (intent.getAction().equals(ACTION_DISABLE)) { + bean.setIsEnable(false); + LogUtils.d(TAG, "setIsEnable(false);"); + TestDemoBindServiceBean.saveBean(this, bean); + } + } + + run(); + + return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); + //return super.onStartCommand(intent, flags, startId); + } + + void run() { + LogUtils.d(TAG, "run()"); + TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); + if (bean == null) { + bean = new TestDemoBindServiceBean(); + TestDemoBindServiceBean.saveBean(this, bean); + } + if (bean.isEnable()) { + LogUtils.d(TAG, "run() bean.isEnable()"); + TestThread.getInstance(this).start(); + LogUtils.d(TAG, "_TestThread.start()"); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy()"); + TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); + if (bean == null) { + bean = new TestDemoBindServiceBean(); + } + + TestThread.getInstance(this).setIsExit(true); + + // 预防 APPBase 应用重启绑定失效。 + // 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。 + // 这样 APPBase 就会用组件方式启动本服务。 + if (bean.isEnable()) { + if (App.isDebuging()) { + SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName()); + } else { + SOS.sosToAppBase(this, TestDemoBindService.class.getName()); + } + } + + _IsRunning = false; + } + + static class TestThread extends Thread { + + volatile static TestThread _TestThread; + Context mContext; + volatile boolean isStarted = false; + volatile boolean isExit = false; + + TestThread(Context context) { + super(); + mContext = context; + } + + public static synchronized TestThread getInstance(Context context) { + if (_TestThread != null) { + _TestThread.setIsExit(true); + } + _TestThread = new TestThread(context); + + return _TestThread; + } + + public synchronized void setIsExit(boolean isExit) { + this.isExit = isExit; + } + + public boolean isExit() { + return isExit; + } + + @Override + public void run() { + if (isStarted == false) { + isStarted = true; + super.run(); + LogUtils.d(TAG, "run() start"); + if (App.isDebuging()) { + WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName()); + } else { + WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName()); + } + + while (!isExit()) { + LogUtils.d(TAG, "run()"); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + LogUtils.d(TAG, "run() exit"); + } + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java new file mode 100644 index 00000000..7364b9bc --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java @@ -0,0 +1,156 @@ +package cc.winboll.studio.winboll.services; + +/** + * @Author ZhanGSKen + * @Date 2025/03/07 12:39:24 + * @Describe 普通服务示例 + */ +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import cc.winboll.studio.winboll.models.TestDemoServiceBean; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.models.TestDemoServiceBean; + +public class TestDemoService extends Service { + + public static final String TAG = "TestDemoService"; + + public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE"; + public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE"; + + volatile static TestThread _TestThread; + + volatile static boolean _IsRunning; + + public synchronized static void setIsRunning(boolean isRunning) { + _IsRunning = isRunning; + } + + public static boolean isRunning() { + return _IsRunning; + } + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + public class MyBinder extends Binder { + public TestDemoService getService() { + return TestDemoService.this; + } + } + + @Override + public void onCreate() { + super.onCreate(); + LogUtils.d(TAG, "onCreate()"); + + + run(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "onStartCommand(...)"); + TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class); + if (bean == null) { + bean = new TestDemoServiceBean(); + } + + if (intent.getAction() != null) { + if (intent.getAction().equals(ACTION_ENABLE)) { + bean.setIsEnable(true); + LogUtils.d(TAG, "setIsEnable(true);"); + TestDemoServiceBean.saveBean(this, bean); + } else if (intent.getAction().equals(ACTION_DISABLE)) { + bean.setIsEnable(false); + LogUtils.d(TAG, "setIsEnable(false);"); + TestDemoServiceBean.saveBean(this, bean); + } + } + + run(); + + return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); + //return super.onStartCommand(intent, flags, startId); + } + + void run() { + LogUtils.d(TAG, "run()"); + TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class); + if (bean == null) { + bean = new TestDemoServiceBean(); + TestDemoServiceBean.saveBean(this, bean); + } + if (bean.isEnable()) { + LogUtils.d(TAG, "run() bean.isEnable()"); + TestThread.getInstance(this).start(); + LogUtils.d(TAG, "_TestThread.start()"); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy()"); + TestThread.getInstance(this).setIsExit(true); + + _IsRunning = false; + } + + static class TestThread extends Thread { + + volatile static TestThread _TestThread; + Context mContext; + volatile boolean isStarted = false; + volatile boolean isExit = false; + + TestThread(Context context) { + super(); + mContext = context; + } + + public static synchronized TestThread getInstance(Context context) { + if (_TestThread != null) { + _TestThread.setIsExit(true); + } + _TestThread = new TestThread(context); + + return _TestThread; + } + + public synchronized void setIsExit(boolean isExit) { + this.isExit = isExit; + } + + public boolean isExit() { + return isExit; + } + + @Override + public void run() { + if (isStarted == false) { + isStarted = true; + super.run(); + LogUtils.d(TAG, "run() start"); + + while (!isExit()) { + LogUtils.d(TAG, "run()"); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + LogUtils.d(TAG, "run() exit"); + } + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java new file mode 100644 index 00000000..c45d8eb5 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java @@ -0,0 +1,59 @@ +package cc.winboll.studio.winboll.sos; + +/** + * @Author ZhanGSKen + * @Date 2025/03/02 09:36:29 + * @Describe WinBoLL 应用 SOS 机理保护类 + */ +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; +import java.io.IOException; + +public class SOS { + + public static final String TAG = "SOS"; + + public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS"; + public static final String EXTRA_OBJECT = "EXTRA_OBJECT"; + + public static void sosToAppBase(Context context, String sosService) { + LogUtils.d(TAG, "sosToAppBase()"); + String szToPackage = "cc.winboll.studio.appbase"; + sos(context, szToPackage, sosService); + + } + + public static void sosToAppBaseBeta(Context context, String sosService) { + LogUtils.d(TAG, "sosToAppBaseBeta()"); + String szToPackage = "cc.winboll.studio.appbase.beta"; + sos(context, szToPackage, sosService); + + } + + static void sos(Context context, String szToPackage, String sosService) { + LogUtils.d(TAG, "sos(...)"); + Intent intent = new Intent(ACTION_SOS); + intent.putExtra(EXTRA_OBJECT, genSOSObject(context.getPackageName(), sosService)); + intent.setPackage(szToPackage); + LogUtils.d(TAG, String.format("ACTION_SOS :\nTo Package : %sSOS Service : %s\n", szToPackage, sosService)); + context.sendBroadcast(intent); + } + + public static SOSObject parseSOSObject(String szSOSObject) { + try { + return SOSObject.parseStringToBean(szSOSObject, SOSObject.class); + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return null; + } + + public static String sosObjectToString(SOSObject object) { + return object.toString(); + } + + public static String genSOSObject(String objectPackageName, String objectServiveName) { + return (new SOSObject(objectPackageName, objectServiveName)).toString(); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java new file mode 100644 index 00000000..039d8e6e --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java @@ -0,0 +1,182 @@ +package cc.winboll.studio.winboll.sos; + +/** + * @Author ZhanGSKen + * @Date 2025/02/27 14:00:21 + * @Describe Simple Operate Signal Service Center. + * 简单操作信号服务中心 + */ +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; +import java.io.FileDescriptor; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; + +public class SOSCenterService extends Service { + + public static final String TAG = "SOSCenterService"; + + private final IBinder binder =(IBinder)new SOSBinder(); + + SOSCenterServiceModel mSOSCenterServiceModel; + static MainThread _MainThread; + public static synchronized MainThread getMainThreadInstance() { + if (_MainThread == null) { + _MainThread = new MainThread(); + } + return _MainThread; + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + public class SOSBinder implements IBinder { + + @Override + public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException { + } + + @Override + public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException { + } + + @Override + public String getInterfaceDescriptor() throws RemoteException { + return null; + } + + @Override + public boolean isBinderAlive() { + return false; + } + + @Override + public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException { + } + + @Override + public boolean pingBinder() { + return false; + } + + @Override + public IInterface queryLocalInterface(String string) { + return null; + } + + @Override + public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException { + return false; + } + + @Override + public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) { + return false; + } + + public static final String TAG = "SOSBinder"; + SOSCenterService getService() { + return SOSCenterService.this; + } + } + + @Override + public void onCreate() { + super.onCreate(); + LogUtils.d(TAG, "onCreate"); + mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class); + if(mSOSCenterServiceModel == null) { + mSOSCenterServiceModel = new SOSCenterServiceModel(); + SOSCenterServiceModel.saveBean(this, mSOSCenterServiceModel); + } + runMainThread(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "onStartCommand"); + + runMainThread(); + + return mSOSCenterServiceModel.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); + } + + void runMainThread() { + mSOSCenterServiceModel = mSOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class); + if (mSOSCenterServiceModel.isEnable() + && _MainThread == null) { + getMainThreadInstance().start(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy"); + mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class); + if (mSOSCenterServiceModel.isEnable()) { + LogUtils.d(TAG, "mSOSCenterServiceModel.isEnable()"); +// ISOSAPP iSOSAPP = (ISOSAPP)getApplication(); +// iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp()); + } + if (_MainThread != null) { + _MainThread.isExist = true; + _MainThread = null; + } + } + + public static void stopISOSService(Context context) { + LogUtils.d(TAG, "stopISOSService"); + SOSCenterServiceModel bean = new SOSCenterServiceModel(); + bean.setIsEnable(false); + SOSCenterServiceModel.saveBean(context, bean); + context.stopService(new Intent(context, SOSCenterServiceModel.class)); + } + + public static void startISOSService(Context context) { + LogUtils.d(TAG, "startISOSService"); + SOSCenterServiceModel bean = new SOSCenterServiceModel(); + bean.setIsEnable(true); + SOSCenterServiceModel.saveBean(context, bean); + context.startService(new Intent(context, SOSCenterServiceModel.class)); + } + + public String getMessage() { + return "Hello from SOSCenterServiceModel"; + } + + static class MainThread extends Thread { + volatile boolean isExist = false; + + public void setIsExist(boolean isExist) { + this.isExist = isExist; + } + + public boolean isExist() { + return isExist; + } + + @Override + public void run() { + super.run(); + while (!isExist) { + LogUtils.d(TAG, "run"); + try { + sleep(1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + } + + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java new file mode 100644 index 00000000..3f226ae7 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java @@ -0,0 +1,69 @@ +package cc.winboll.studio.winboll.sos; + +/** + * @Author ZhanGSKen + * @Date 2025/03/02 09:49:45 + * @Describe SOSCenterServiceModel + * Simple Operate Signal Service Model. + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class SOSCenterServiceModel extends BaseBean { + + public static final String TAG = "SOSCenterServiceModel"; + + boolean isEnable; + + public SOSCenterServiceModel() { + this.isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return SOSCenterServiceModel.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("isEnable").value(isEnable()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("isEnable")) { + setIsEnable(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java new file mode 100644 index 00000000..14dd36a0 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java @@ -0,0 +1,29 @@ +package cc.winboll.studio.winboll.sos; + +/** + * @Author ZhanGSKen + * @Date 2025/02/27 14:04:35 + * @Describe SOSCenterServiceReceiver + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; + +public class SOSCenterServiceReceiver extends BroadcastReceiver { + + public static final String TAG = "SOSCenterServiceReceiver"; + + public static final String ACTION_SOS = SOSCenterServiceReceiver.class.getName() + ".ACTION_SOS"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_SOS)) { + // 处理接收到的广播消息 + LogUtils.d(TAG, String.format("Action %s \n%s\n%s", action)); + } else { + LogUtils.d(TAG, String.format("%s", action)); + } + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java new file mode 100644 index 00000000..ec07b388 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java @@ -0,0 +1,86 @@ +package cc.winboll.studio.winboll.sos; + +/** + * @Author ZhanGSKen + * @Date 2025/02/27 14:12:05 + * @Describe SOSBean + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class SOSObject extends BaseBean { + + public static final String TAG = "SOSObject"; + + String objectPackageName; + String objectServiveName; + + public SOSObject() { + this.objectPackageName = ""; + this.objectServiveName = ""; + } + + public SOSObject(String objectPackageName, String objectServiveName) { + this.objectPackageName = objectPackageName; + this.objectServiveName = objectServiveName; + } + + public void setObjectPackageName(String objectPackageName) { + this.objectPackageName = objectPackageName; + } + + public String getObjectPackageName() { + return objectPackageName; + } + + public void setObjectServiveName(String objectServiveName) { + this.objectServiveName = objectServiveName; + } + + public String getObjectServiveName() { + return objectServiveName; + } + + @Override + public String getName() { + return SOSObject.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("objectPackageName").value(getObjectPackageName()); + jsonWriter.name("objectServiveName").value(getObjectServiveName()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("objectPackageName")) { + setObjectPackageName(jsonReader.nextString()); + } else if (name.equals("objectServiveName")) { + setObjectServiveName(jsonReader.nextString()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java b/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java new file mode 100644 index 00000000..80ecc3f9 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java @@ -0,0 +1,54 @@ +package cc.winboll.studio.winboll.threads; + +/** + * @Author ZhanGSKen + * @Date 2025/02/14 03:46:44 + */ +import android.content.Context; +import cc.winboll.studio.winboll.handlers.MainServiceHandler; +import cc.winboll.studio.libappbase.LogUtils; +import java.lang.ref.WeakReference; + +public class MainServiceThread extends Thread { + + public static final String TAG = "MainServiceThread"; + + Context mContext; + + // 控制线程是否退出的标志 + volatile boolean isExist = false; + + // 服务Handler, 用于线程发送消息使用 + WeakReference mwrMainServiceHandler; + + public void setIsExist(boolean isExist) { + this.isExist = isExist; + } + + public boolean isExist() { + return isExist; + } + + public MainServiceThread(Context context, MainServiceHandler handler) { + mContext = context; + mwrMainServiceHandler = new WeakReference(handler); + } + + @Override + public void run() { + LogUtils.d(TAG, "run()"); + + while (!isExist()) { + //ToastUtils.show("run()"); + //LogUtils.d(TAG, "run()"); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + LogUtils.d(TAG, "run() exit."); + } + +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/APPPlusUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/APPPlusUtils.java new file mode 100644 index 00000000..2004f939 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/APPPlusUtils.java @@ -0,0 +1,163 @@ +package cc.winboll.studio.winboll.utils; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.widget.Toast; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.winboll.App; +import cc.winboll.studio.winboll.R; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/27 08:56 + * @Describe APPPlusUtils + */ +public class APPPlusUtils { + public static final String TAG = "APPPlusUtils"; + + // 快捷方式配置(名称+图标,需与实际资源匹配) +// private static final String PLUS_SHORTCUT_NAME = "位置服务-Laojun"; +// private static final int PLUS_SHORTCUT_ICON = R.mipmap.ic_launcher; // Laojun 图标资源 + + /** + * 添加Plus组件与图标 + */ + public static boolean switchAppLauncherToComponent(Context context, String componentName) { + if (context == null) { + LogUtils.d(TAG, "切换失败:上下文为空"); + Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败", Toast.LENGTH_SHORT).show(); + return false; + } + + PackageManager pm = context.getPackageManager(); + + ComponentName plusComponentSwitchTo = new ComponentName(context, componentName); + ComponentName plusComponentEN1 = new ComponentName(context, App.COMPONENT_EN1); + ComponentName plusComponentCN1 = new ComponentName(context, App.COMPONENT_CN1); + ComponentName plusComponentCN2 = new ComponentName(context, App.COMPONENT_CN2); + + try { + disableComponent(pm, plusComponentEN1); + disableComponent(pm, plusComponentCN1); + disableComponent(pm, plusComponentCN2); + enableComponent(pm, plusComponentSwitchTo); + + return true; + + } catch (Exception e) { + LogUtils.e(TAG, "图标切换失败:" + e.getMessage()); + Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败" + e.getMessage(), Toast.LENGTH_SHORT).show(); + return false; + } + } + + /** + * 创建指定组件的桌面快捷方式(自动去重,兼容 Android 8.0+) + * @param component 目标组件(如 LAOJUN_ACTIVITY) + * @param name 快捷方式名称 + * @param iconRes 快捷方式图标资源ID + * @return 是否创建成功 + */ + private static boolean createComponentShortcut(Context context, ComponentName component, String name, int iconRes) { + if (context == null || component == null || name == null || iconRes == 0) { + LogUtils.d(TAG, "快捷方式创建失败:参数为空"); + return false; + } + + // Android 8.0+(API 26+):使用 ShortcutManager(系统推荐) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + PackageManager pm = context.getPackageManager(); + android.content.pm.ShortcutManager shortcutManager = context.getSystemService(android.content.pm.ShortcutManager.class); + if (shortcutManager == null || !shortcutManager.isRequestPinShortcutSupported()) { + LogUtils.d(TAG, "系统不支持创建快捷方式"); + return false; + } + + // 检查是否已存在该组件的快捷方式(去重) + for (android.content.pm.ShortcutInfo info : shortcutManager.getPinnedShortcuts()) { + if (component.getClassName().equals(info.getIntent().getComponent().getClassName())) { + LogUtils.d(TAG, "快捷方式已存在:" + component.getClassName()); + return true; + } + } + + // 构建启动目标组件的意图 + Intent launchIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(component) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + + // 构建快捷方式信息 + android.content.pm.ShortcutInfo shortcutInfo = new android.content.pm.ShortcutInfo.Builder(context, component.getClassName()) + .setShortLabel(name) + .setLongLabel(name) + .setIcon(android.graphics.drawable.Icon.createWithResource(context, iconRes)) + .setIntent(launchIntent) + .build(); + + // 请求创建快捷方式(需用户确认) + shortcutManager.requestPinShortcut(shortcutInfo, null); + return true; + + } catch (Exception e) { + LogUtils.d(TAG, "Android O+ 快捷方式创建失败:" + e.getMessage()); + return false; + } + } else { + // Android 8.0 以下:使用广播(兼容旧机型) + try { + // 构建启动目标组件的意图 + Intent launchIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(component) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + + // 构建创建快捷方式的广播意图 + Intent installIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); + installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent); + installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(context, iconRes)); + installIntent.putExtra("duplicate", false); // 禁止重复创建 + + context.sendBroadcast(installIntent); + return true; + + } catch (Exception e) { + LogUtils.d(TAG, "Android O- 快捷方式创建失败:" + e.getMessage()); + return false; + } + } + } + + /** + * 启用组件(带状态检查,避免重复操作) + */ + private static void enableComponent(PackageManager pm, ComponentName component) { + if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + pm.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS + ); + } + } + + /** + * 禁用组件(带状态检查,避免重复操作) + */ + private static void disableComponent(PackageManager pm, ComponentName component) { + if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { + pm.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS + ); + } + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/RSAUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/RSAUtils.java new file mode 100644 index 00000000..5258e2b7 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/RSAUtils.java @@ -0,0 +1,222 @@ +package cc.winboll.studio.winboll.utils; + +/** + * @Author ZhanGSKen + * @Date 2025/06/04 13:36 + * @Describe RSA加密工具 + */ +import android.content.Context; +import android.util.Base64; +import cc.winboll.studio.libappbase.LogUtils; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Objects; +import javax.crypto.Cipher; + +public class RSAUtils { + private static final String TAG = "RSAUtils"; + private static final int KEY_SIZE = 2048; + private static final String KEY_ALGORITHM = "RSA"; + private static final String PUBLIC_KEY_FILE = "public.key"; + private static final String PRIVATE_KEY_FILE = "private.key"; + private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式 + + private final String keyPath; + private static volatile RSAUtils INSTANCE; + + /** + * 构造方法:初始化密钥存储路径(内部存储) + */ + private RSAUtils(Context context) { + keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式 + } + + /** + * 获取单例实例 + */ + public static synchronized RSAUtils getInstance(Context context) { + if (INSTANCE == null) { + INSTANCE = new RSAUtils(context); + } + return INSTANCE; + } + + /** + * 检查密钥文件是否存在 + */ + public boolean keysExist() { + File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE); + File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE); + return publicKeyFile.exists() && privateKeyFile.exists(); + } + + /** + * 生成密钥对并保存到文件 + */ + public void generateAndSaveKeys() throws Exception { + LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)"); + KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + generator.initialize(KEY_SIZE); + KeyPair keyPair = generator.generateKeyPair(); + + saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded()); + saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); + LogUtils.d(TAG, "密钥对生成并保存成功"); + } + + /** + * 获取或生成密钥对(线程安全) + */ + public KeyPair getOrGenerateKeys() throws Exception { + if (!keysExist()) { + synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成 + if (!keysExist()) { + generateAndSaveKeys(); + } + } + } + return readKeysFromFile(); + } + + /** + * 从文件读取密钥对 + */ + private KeyPair readKeysFromFile() throws Exception { + LogUtils.d(TAG, "读取密钥对文件"); + try { + byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE); + byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE); + + X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); + PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes); + + KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicKey = factory.generatePublic(publicSpec); + PrivateKey privateKey = factory.generatePrivate(privateSpec); + + return new KeyPair(publicKey, privateKey); + } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { + LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage()); + throw new Exception("密钥文件损坏或格式错误", e); + } + } + + /** + * 保存密钥到文件(通用方法) + */ + private void saveKey(String fileName, byte[] keyBytes) throws IOException { + Objects.requireNonNull(keyBytes, "密钥字节数据不可为空"); + File dir = new File(keyPath); + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("创建密钥目录失败:" + keyPath); + } + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(keyPath + fileName); + fos.write(keyBytes); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); + } + } + } + } + + /** + * 读取文件为字节数组(Java 7 兼容) + */ + private byte[] readFileToBytes(String filePath) throws IOException { + File file = new File(filePath); + if (!file.exists() || file.isDirectory()) { + throw new IOException("文件不存在或为目录:" + filePath); + } + + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + byte[] data = new byte[(int) file.length()]; + int bytesRead = fis.read(data); + if (bytesRead != data.length) { + throw new IOException("文件读取不完整"); + } + return data; + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); + } + } + } + } + + /** + * 公钥加密(带参数校验) + */ + public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception { + Objects.requireNonNull(plainText, "明文不可为空"); + Objects.requireNonNull(publicKey, "公钥不可为空"); + + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + + // 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding) + int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度 + if (plainText.getBytes("UTF-8").length > maxPlainTextSize) { + throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节"); + } + + return cipher.doFinal(plainText.getBytes("UTF-8")); + } + + /** + * 私钥解密(带参数校验) + */ + public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception { + Objects.requireNonNull(encryptedData, "密文不可为空"); + Objects.requireNonNull(privateKey, "私钥不可为空"); + + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] decryptedBytes = cipher.doFinal(encryptedData); + return new String(decryptedBytes, "UTF-8"); + } + /** + * 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容) + * @param httpString Base64 字符串(非 null) + * @return 加密字节数组 + * @throws IllegalArgumentException 解码失败时抛出 + */ + public byte[] httpStringToEncryptBytes(String httpString) { + Objects.requireNonNull(httpString, "HTTP 字符串不可为空"); + + // 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接) + int pad = httpString.length() % 4; + StringBuilder paddedString = new StringBuilder(httpString); + if (pad != 0) { + for (int i = 0; i < pad; i++) { + paddedString.append('='); // 补全 '=' + } + } + + // 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7) + return Base64.decode(paddedString.toString(), Base64.URL_SAFE); + } +} + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java new file mode 100644 index 00000000..959169a5 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java @@ -0,0 +1,33 @@ +package cc.winboll.studio.winboll.utils; + +/** + * @Author ZhanGSKen + * @Date 2025/06/08 09:05 + * @Describe Termux 应用操作工具集 + */ +import android.content.Intent; + +public abstract class TermuxUtils { + + public static final String TAG = "TermuxUtils"; + + private void runTermuxCommand(String command) { + // 1. 创建 Intent,指定 Termux 的 RunCommandService + /*Intent intent = new Intent("com.termux.RUN_COMMAND"); + intent.setPackage("com.termux"); // Termux 应用的包名 + + // 2. 传递命令参数(必填) + //intent.putExtra("command", command); + intent.putExtra("cd ~/WinBoLL&&echo 'WinBoLL cmd exec at (data", command); + + // 3. 可选:设置工作目录(默认为 Termux 的 home 目录) + intent.putExtra("dir", "/data/data/com.termux/files/home/WinBoLL"); + + // 4. 发送 Intent(需处理可能的安全异常或 ActivityNotFoundException) + try { + getApplicationContext().startService(intent); + } catch (Exception e) { + e.printStackTrace(); + }*/ + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/utils/YunUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/YunUtils.java new file mode 100644 index 00000000..d0d36f92 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/YunUtils.java @@ -0,0 +1,282 @@ +package cc.winboll.studio.winboll.utils; + +/** + * @Author ZhanGSKen + * @Date 2025/06/04 17:21 + * @Describe 应用登录与接口工具 + */ +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import cc.winboll.studio.libappbase.LogUtils; +import com.google.gson.Gson; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.concurrent.TimeUnit; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import cc.winboll.studio.winboll.models.UserInfoModel; +import cc.winboll.studio.winboll.models.ResponseData; +import cc.winboll.studio.libapputils.utils.FileUtils; + +public class YunUtils { + public static final String TAG = "YunUtils"; + // 私有静态实例,类加载时创建 + private static volatile YunUtils INSTANCE; + Context mContext; + UserInfoModel mUserInfoModel; + String token = ""; + String mDataFolderPath = ""; + String mUserInfoModelPath = ""; + + private static final int CONNECT_TIMEOUT = 15; // 连接超时时间(秒) + private static final int READ_TIMEOUT = 20; // 读取超时时间(秒) + private static volatile YunUtils instance; + private OkHttpClient okHttpClient; + private Handler mainHandler; // 主线程 Handler + + // 私有构造方法,防止外部实例化 + private YunUtils(Context context) { + LogUtils.d(TAG, "YunUtils"); + mContext = context; + mDataFolderPath = mContext.getExternalFilesDir(TAG).toString(); + File fTest = new File(mDataFolderPath); + if (!fTest.exists()) { + fTest.mkdirs(); + } + mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson"; + + okHttpClient = new OkHttpClient.Builder() + .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .build(); + mainHandler = new Handler(Looper.getMainLooper()); // 获取主线程 Looper + } + + // 公共静态方法,返回唯一实例 + public static synchronized YunUtils getInstance(Context context) { + LogUtils.d(TAG, "getInstance"); + if (INSTANCE == null) { + INSTANCE = new YunUtils(context); + } + return INSTANCE; + } + + public void checkLoginStatus() { + String token = getLocalToken(); + LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token)); + } + + String getLocalToken() { + UserInfoModel userInfoModel = loadUserInfoModel(); + return (userInfoModel == null) ?"": userInfoModel.getToken(); + } + + public void login(String host, UserInfoModel userInfoModel) { + LogUtils.d(TAG, "login"); + + // 发送 POST 请求 + String apiUrl = host + "/login/index.php"; + // 序列化对象为JSON + Gson gson = new Gson(); + String jsonData = gson.toJson(userInfoModel); // 自动生成标准JSON + //String jsonData = userInfoModel.toString(); + LogUtils.d(TAG, "要发送的数据 : " + jsonData); + + sendPostRequest(apiUrl, jsonData, new OnResponseListener() { + // 成功回调(主线程) + @Override + public void onSuccess(String responseBody) { + LogUtils.d(TAG, "onSuccess"); + LogUtils.d(TAG, String.format("responseBody %s", responseBody)); + Gson gson = new Gson(); + ResponseData result = gson.fromJson(responseBody, ResponseData.class); // 转为 Result 实例 + if(result.getStatus().equals(ResponseData.STATUS_SUCCESS)) { + + UserInfoModel userInfoModel = result.getData(); + if (userInfoModel != null) { + LogUtils.d(TAG, "收到网站 UserInfoModel"); + String token = userInfoModel.getToken(); + saveLocalToken(token); + checkLoginStatus(); + } + + } else if(result.getStatus().equals(ResponseData.STATUS_ERROR)) { + try { + String decodedMessage = URLDecoder.decode(result.getMessage(), "UTF-8"); + LogUtils.d(TAG, "服务器返回信息: " + decodedMessage); + } catch (UnsupportedEncodingException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + } + + // 失败回调(主线程) + @Override + public void onFailure(String errorMsg) { + LogUtils.d(TAG, errorMsg); + // 处理错误 + } + }); + } + + public void saveLocalToken(String token) { + UserInfoModel userInfoModel = new UserInfoModel(); + userInfoModel.setToken(token); + saveUserInfoModel(userInfoModel); + } + + UserInfoModel loadUserInfoModel() { + LogUtils.d(TAG, "loadUserInfoModel"); + if (new File(mUserInfoModelPath).exists()) { + try { + // 加载加密后的模型数据 + byte[] encryptedData = FileUtils.readByteArrayFromFile(mUserInfoModelPath); + // 加载 RSA 工具 + RSAUtils utils = RSAUtils.getInstance(mContext); + KeyPair keyPair = utils.getOrGenerateKeys(); + //PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + // 私钥解密模型数据 + String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); + LogUtils.d(TAG, String.format("szInfo %s", szInfo)); + mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class); + if (mUserInfoModel == null) { + LogUtils.d(TAG, "模型数据解析为空数据。"); + } + LogUtils.d(TAG, "UserInfoModel 解密加载结束。"); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } else { + LogUtils.d(TAG, "云服务登录信息不存在。"); + mUserInfoModel = null; + } + return mUserInfoModel; + } + + void saveUserInfoModel(UserInfoModel userInfoModel) { + LogUtils.d(TAG, "saveUserInfoModel"); + try { + String szInfo = userInfoModel.toString(); + LogUtils.d(TAG, "原始数据: " + szInfo); + + RSAUtils utils = RSAUtils.getInstance(mContext); + KeyPair keyPair = utils.getOrGenerateKeys(); + PublicKey publicKey = keyPair.getPublic(); + + // 公钥加密(传入字节数组,避免中间字符串转换) + byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey); + + // 保存加密字节数组到文件(直接操作字节,无需转字符串) + FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath); + LogUtils.d(TAG, "加密数据已保存"); + + // 测试解密(仅调试用) + String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); + LogUtils.d(TAG, "解密结果: " + szInfo2); + + mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class); + if (mUserInfoModel == null) { + LogUtils.d(TAG, "模型解析失败"); + } + } catch (Exception e) { + LogUtils.d(TAG, "加密/解密失败: " + e.getMessage()); + } + } + + // 发送 POST 请求(JSON 数据) + public void sendPostRequest(String url, String data, OnResponseListener listener) { + RequestBody requestBody = RequestBody.create( + MediaType.parse("application/json; charset=utf-8"), // 关键头信息 + data.getBytes(StandardCharsets.UTF_8) + ); + + Request request = new Request.Builder() + .url(url) + .post(requestBody) + .addHeader("Content-Type", "application/json") // 显式添加头 + .build(); + + executeRequest(request, listener); + } + + // 发送 GET 请求 + public void sendGetRequest(String url, OnResponseListener listener) { + Request request = new Request.Builder() + .url(url) + .get() + .build(); + executeRequest(request, listener); + } + + // 执行请求(子线程处理) + private void executeRequest(final Request request, final OnResponseListener listener) { + okHttpClient.newCall(request).enqueue(new Callback() { + // 响应成功(子线程) + @Override + public void onResponse(Call call, Response response) throws IOException { + try { + if (!response.isSuccessful()) { + postFailure(listener, "响应码错误:" + response.code()); + return; + } + String responseBody = response.body().string(); + postSuccess(listener, responseBody); + } catch (Exception e) { + postFailure(listener, "解析失败:" + e.getMessage()); + } + } + + // 响应失败(子线程) + @Override + public void onFailure(Call call, IOException e) { + postFailure(listener, "网络失败:" + e.getMessage()); + } + + // 主线程回调(使用 Handler) + private void postSuccess(final OnResponseListener listener, final String msg) { + mainHandler.post(new Runnable() { + @Override + public void run() { + listener.onSuccess(msg); + } + }); + } + + private void postFailure(final OnResponseListener listener, final String msg) { + mainHandler.post(new Runnable() { + @Override + public void run() { + listener.onFailure(msg); + } + }); + } + }); + } + + public interface OnResponseListener { + /** + * 成功响应(主线程回调) + * @param responseBody 响应体字符串 + */ + void onSuccess(String responseBody); + + /** + * 失败回调(包含错误信息) + * @param errorMsg 错误描述 + */ + void onFailure(String errorMsg); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java new file mode 100644 index 00000000..e5d5e8d8 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java @@ -0,0 +1,186 @@ +package cc.winboll.studio.winboll.widgets; +/** + * @Author ZhanGSKen + * @Date 2025/02/15 14:41:25 + * @Describe TimeWidget + */ +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViews; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libapputils.utils.AppUtils; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.winboll.WinBoLL; +import cc.winboll.studio.winboll.models.WinBoLLModel; +import cc.winboll.studio.winboll.models.WinBoLLNewsBean; +import cc.winboll.studio.winboll.receivers.APPNewsWidgetClickListener; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +public class APPNewsWidget extends AppWidgetProvider { + + public static final String TAG = "APPNewsWidget"; + + public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE"; + public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT"; + + + volatile static ArrayList _WinBoLLNewsBeanList; + final static int _MAX_PAGES = 10; + final static int _OnePageLinesCount = 5; + volatile static int _CurrentPageIndex = 0; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + initWinBoLLNewsBeanList(context); + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + initWinBoLLNewsBeanList(context); + if (intent.getAction().equals(ACTION_RELOAD_REPORT)) { + LogUtils.d(TAG, "ACTION_RELOAD_REPORT"); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class)); + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + }else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) { + LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE"); + String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL); + LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel)); + if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) { + try { + WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class); + if (bean != null) { + String szAppPackageName = bean.getAppPackageName(); + LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName)); + String szAppMainServiveName = bean.getAppMainServiveName(); + LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName)); + + + String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName); + LogUtils.d(TAG, String.format("appName %s", appName)); + WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName); + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); + String currentTime = sdf.format(new Date()); + StringBuilder sbLine = new StringBuilder(); + sbLine.append("["); + sbLine.append(currentTime); + sbLine.append("] Wake up "); + sbLine.append(appName); + winBollNewsBean.setMessage(sbLine.toString()); + + addWinBoLLNewsBean(context, winBollNewsBean); + + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class)); + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + } + } + + // + // 加入新报告信息 + // + public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) { + initWinBoLLNewsBeanList(context); + _WinBoLLNewsBeanList.add(0, bean); + // 控制记录总数 + while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) { + _WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1); + } + WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); + } + + synchronized static void initWinBoLLNewsBeanList(Context context) { + if (_WinBoLLNewsBeanList == null) { + _WinBoLLNewsBeanList = new ArrayList(); + WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); + } + if (_WinBoLLNewsBeanList == null) { + _WinBoLLNewsBeanList = new ArrayList(); + WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); + } + } + + private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + LogUtils.d(TAG, "updateAppWidget(...)"); + + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news); + //设置按钮点击事件 + Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class); + intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE); + PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT); + views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre); + Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class); + intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT); + PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT); + views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext); + + views.setTextViewText(R.id.tv_msg, getPageInfo()); + views.setTextViewText(R.id.tv_news, getMessage()); + appWidgetManager.updateAppWidget(appWidgetId, views); + } + + public static String getMessage() { + ArrayList msgTemp = new ArrayList(); + if (_WinBoLLNewsBeanList != null) { + int start = _OnePageLinesCount * _CurrentPageIndex; + start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1; + for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) { + msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage()); + } + String message = String.join("\n", msgTemp); + return message; + } + return ""; + } + + public static void prePage(Context context) { + if (_WinBoLLNewsBeanList != null) { + if (_CurrentPageIndex > 0) { + _CurrentPageIndex = _CurrentPageIndex - 1; + } + Intent intentWidget = new Intent(context, APPNewsWidget.class); + intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); + context.sendBroadcast(intentWidget); + } + } + + public static void nextPage(Context context) { + if (_WinBoLLNewsBeanList != null) { + if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) { + _CurrentPageIndex = _CurrentPageIndex + 1; + } + Intent intentWidget = new Intent(context, APPNewsWidget.class); + intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); + context.sendBroadcast(intentWidget); + } + } + + String getPageInfo() { + if (_WinBoLLNewsBeanList == null) { + return "0/0"; + } + int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount; + int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1); + return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java new file mode 100644 index 00000000..454a78b4 --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java @@ -0,0 +1,59 @@ +package cc.winboll.studio.winboll.widgets; + +/** + * @Author ZhanGSKen + * @Date 2025/02/17 20:32:12 + */ +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViews; +import cc.winboll.studio.winboll.R; +import cc.winboll.studio.libappbase.ToastUtils; + +public class StatusWidget extends AppWidgetProvider { + + public static final String TAG = "StatusWidget"; + + public static final String ACTION_STATUS_UPDATE = "cc.winboll.studio.libappbase.widgets.APPWidget.ACTION_STATUS_UPDATE"; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + if (intent.getAction().equals(ACTION_STATUS_UPDATE)) { + ToastUtils.show("Test"); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, StatusWidget.class)); + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + } + + private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_status); + //设置按钮点击事件 + Intent intentAppButton = new Intent(context, StatusWidgetClickListener.class); + intentAppButton.setAction(StatusWidgetClickListener.ACTION_IVAPP); + PendingIntent pendingIntentAppButton = PendingIntent.getBroadcast(context, 0, intentAppButton, PendingIntent.FLAG_UPDATE_CURRENT); + views.setOnClickPendingIntent(R.id.ivapp, pendingIntentAppButton); + +// boolean isActive = ServiceUtils.isServiceRunning(context, TestService.class.getName()); +// if (isActive) { +// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher); +// } else { +// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher_disable); +// } + appWidgetManager.updateAppWidget(appWidgetId, views); + } +} diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java new file mode 100644 index 00000000..ae182cec --- /dev/null +++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java @@ -0,0 +1,33 @@ +package cc.winboll.studio.winboll.widgets; + +/** + * @Author ZhanGSKen + * @Date 2025/02/17 20:33:53 + * @Describe APPWidgetClickListener + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; + +public class StatusWidgetClickListener extends BroadcastReceiver { + + public static final String TAG = "APPWidgetClickListener"; + + public static final String ACTION_IVAPP = "cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + LogUtils.d(TAG, String.format("action %s", action)); + return; + } + if (action.equals(ACTION_IVAPP)) { + ToastUtils.show("ACTION_LAUNCHER"); + } else { + LogUtils.d(TAG, String.format("action %s", action)); + } + } +} diff --git a/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml b/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..c7bd21db --- /dev/null +++ b/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/winboll/src/main/res/drawable/bg_shadow.xml b/winboll/src/main/res/drawable/bg_shadow.xml new file mode 100644 index 00000000..6d3d8989 --- /dev/null +++ b/winboll/src/main/res/drawable/bg_shadow.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_cloud.xml b/winboll/src/main/res/drawable/ic_cloud.xml new file mode 100644 index 00000000..c116648d --- /dev/null +++ b/winboll/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_cloud_outline.xml b/winboll/src/main/res/drawable/ic_cloud_outline.xml new file mode 100644 index 00000000..a8ed00cd --- /dev/null +++ b/winboll/src/main/res/drawable/ic_cloud_outline.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_dev_connected.xml b/winboll/src/main/res/drawable/ic_dev_connected.xml new file mode 100644 index 00000000..1fb2f264 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_dev_connected.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/winboll/src/main/res/drawable/ic_dev_disconnected.xml b/winboll/src/main/res/drawable/ic_dev_disconnected.xml new file mode 100644 index 00000000..42679750 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_dev_disconnected.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/winboll/src/main/res/drawable/ic_email.xml b/winboll/src/main/res/drawable/ic_email.xml new file mode 100644 index 00000000..d526b262 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_email.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_email_alert.xml b/winboll/src/main/res/drawable/ic_email_alert.xml new file mode 100644 index 00000000..f3ed6134 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_email_alert.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_iw.xml b/winboll/src/main/res/drawable/ic_iw.xml new file mode 100644 index 00000000..5c5c1917 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_iw.xml @@ -0,0 +1,35 @@ + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher.xml b/winboll/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 00000000..21f28b1a --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_background.xml b/winboll/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..d5fccc53 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_beta.xml b/winboll/src/main/res/drawable/ic_launcher_beta.xml new file mode 100644 index 00000000..c304fdc1 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher_beta.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_disable.xml b/winboll/src/main/res/drawable/ic_launcher_disable.xml new file mode 100644 index 00000000..174f475c --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher_disable.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_foreground.xml b/winboll/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..872b04e2 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,10 @@ + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml b/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml new file mode 100644 index 00000000..4622116b --- /dev/null +++ b/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml @@ -0,0 +1,10 @@ + + + + diff --git a/winboll/src/main/res/drawable/ic_launcher_stage.jpg b/winboll/src/main/res/drawable/ic_launcher_stage.jpg new file mode 100644 index 00000000..4d7d620b Binary files /dev/null and b/winboll/src/main/res/drawable/ic_launcher_stage.jpg differ diff --git a/winboll/src/main/res/drawable/ic_miapp.png b/winboll/src/main/res/drawable/ic_miapp.png new file mode 100644 index 00000000..8d0939b8 Binary files /dev/null and b/winboll/src/main/res/drawable/ic_miapp.png differ diff --git a/winboll/src/main/res/drawable/ic_winboll_help.xml b/winboll/src/main/res/drawable/ic_winboll_help.xml new file mode 100644 index 00000000..564175fa --- /dev/null +++ b/winboll/src/main/res/drawable/ic_winboll_help.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/winboll/src/main/res/drawable/ic_winboll_log.xml b/winboll/src/main/res/drawable/ic_winboll_log.xml new file mode 100644 index 00000000..011f2b2c --- /dev/null +++ b/winboll/src/main/res/drawable/ic_winboll_log.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_winboll_logo.xml b/winboll/src/main/res/drawable/ic_winboll_logo.xml new file mode 100644 index 00000000..ea28987a --- /dev/null +++ b/winboll/src/main/res/drawable/ic_winboll_logo.xml @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_winboll_point.xml b/winboll/src/main/res/drawable/ic_winboll_point.xml new file mode 100644 index 00000000..48028cc4 --- /dev/null +++ b/winboll/src/main/res/drawable/ic_winboll_point.xml @@ -0,0 +1,20 @@ + + + + diff --git a/winboll/src/main/res/drawable/shape_gradient.xml b/winboll/src/main/res/drawable/shape_gradient.xml new file mode 100644 index 00000000..c164fe9d --- /dev/null +++ b/winboll/src/main/res/drawable/shape_gradient.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/winboll/src/main/res/drawable/view_border.xml b/winboll/src/main/res/drawable/view_border.xml new file mode 100644 index 00000000..58b374aa --- /dev/null +++ b/winboll/src/main/res/drawable/view_border.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/winboll/src/main/res/layout/activity_about.xml b/winboll/src/main/res/layout/activity_about.xml new file mode 100644 index 00000000..b79821b7 --- /dev/null +++ b/winboll/src/main/res/layout/activity_about.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/winboll/src/main/res/layout/activity_logon.xml b/winboll/src/main/res/layout/activity_logon.xml new file mode 100644 index 00000000..2a2c2d16 --- /dev/null +++ b/winboll/src/main/res/layout/activity_logon.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + +