diff --git a/settings.gradle-demo b/settings.gradle-demo
index cf7adb4..f2b5054 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 0000000..b18c367
--- /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 0000000..894f073
--- /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 0000000..64b4a05
--- /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 0000000..be35225
--- /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 0000000..045e125
--- /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 0000000..42cbd7a
--- /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 0000000..6b6dc9a
--- /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 0000000..2dbb833
--- /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 0000000..4684c41
--- /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 0000000..163eb03
--- /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 0000000..1c7ecee
--- /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 0000000..9fa6d1e
--- /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 0000000..d9d255b
--- /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 0000000..20a70d8
--- /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 0000000..8ad34fc
--- /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 0000000..f17f234
--- /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 0000000..cbdff70
--- /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 0000000..c9d29de
--- /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 0000000..e939b7e
--- /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 0000000..eea24c9
--- /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 0000000..7097711
--- /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 0000000..c4a0bc6
--- /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 0000000..f20d4ec
--- /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 0000000..d906af3
--- /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 0000000..15c5e53
--- /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 0000000..65b2c80
--- /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 0000000..8ab5fb6
--- /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 0000000..a4ab575
--- /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 0000000..effeb43
--- /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 0000000..98b64a1
--- /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 0000000..ed78bdc
--- /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 0000000..ab68051
--- /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 0000000..5899eca
--- /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 0000000..52c4e4c
--- /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 0000000..6b5d65d
--- /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 0000000..e749196
--- /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 0000000..4e3681d
--- /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 0000000..8fa99b9
--- /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 0000000..1bf9d0e
--- /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 0000000..2fd6076
--- /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 0000000..d287377
--- /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 0000000..cb99ad3
--- /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 0000000..7364b9b
--- /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 0000000..c45d8eb
--- /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 0000000..039d8e6
--- /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 0000000..3f226ae
--- /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 0000000..14dd36a
--- /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 0000000..ec07b38
--- /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 0000000..80ecc3f
--- /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 0000000..2004f93
--- /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 0000000..5258e2b
--- /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 0000000..959169a
--- /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 0000000..d0d36f9
--- /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 0000000..e5d5e8d
--- /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 0000000..454a78b
--- /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 0000000..ae182ce
--- /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 0000000..c7bd21d
--- /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 0000000..6d3d898
--- /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 0000000..c116648
--- /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 0000000..a8ed00c
--- /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 0000000..1fb2f26
--- /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 0000000..4267975
--- /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 0000000..d526b26
--- /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 0000000..f3ed613
--- /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 0000000..5c5c191
--- /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 0000000..21f28b1
--- /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 0000000..d5fccc5
--- /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 0000000..c304fdc
--- /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 0000000..174f475
--- /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 0000000..872b04e
--- /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 0000000..4622116
--- /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 0000000..4d7d620
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 0000000..8d0939b
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 0000000..564175f
--- /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 0000000..011f2b2
--- /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 0000000..ea28987
--- /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 0000000..48028cc
--- /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 0000000..c164fe9
--- /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 0000000..58b374a
--- /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 0000000..b79821b
--- /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 0000000..2a2c2d1
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_logon.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_main.xml b/winboll/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..3ea6554
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_main.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_new.xml b/winboll/src/main/res/layout/activity_new.xml
new file mode 100644
index 0000000..b3dd223
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_new.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_new2.xml b/winboll/src/main/res/layout/activity_new2.xml
new file mode 100644
index 0000000..697e613
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_new2.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_winbollunittest.xml b/winboll/src/main/res/layout/activity_winbollunittest.xml
new file mode 100644
index 0000000..135b845
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_winbollunittest.xml
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_yun.xml b/winboll/src/main/res/layout/activity_yun.xml
new file mode 100644
index 0000000..ac73fab
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_yun.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/fragment_main.xml b/winboll/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..4beaf90
--- /dev/null
+++ b/winboll/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/view_toolbar.xml b/winboll/src/main/res/layout/view_toolbar.xml
new file mode 100644
index 0000000..700d939
--- /dev/null
+++ b/winboll/src/main/res/layout/view_toolbar.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/widget_news.xml b/winboll/src/main/res/layout/widget_news.xml
new file mode 100644
index 0000000..e85e3d2
--- /dev/null
+++ b/winboll/src/main/res/layout/widget_news.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/widget_status.xml b/winboll/src/main/res/layout/widget_status.xml
new file mode 100644
index 0000000..b115a06
--- /dev/null
+++ b/winboll/src/main/res/layout/widget_status.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/menu/toolbar_main.xml b/winboll/src/main/res/menu/toolbar_main.xml
new file mode 100644
index 0000000..41657dd
--- /dev/null
+++ b/winboll/src/main/res/menu/toolbar_main.xml
@@ -0,0 +1,20 @@
+
+
diff --git a/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/winboll/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/winboll/src/main/res/mipmap-hdpi/ic_launcher.png b/winboll/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a2f5908
Binary files /dev/null and b/winboll/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/winboll/src/main/res/mipmap-hdpi/ic_launcher_round.png b/winboll/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
Binary files /dev/null and b/winboll/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/winboll/src/main/res/mipmap-mdpi/ic_launcher.png b/winboll/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ff10afd
Binary files /dev/null and b/winboll/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/winboll/src/main/res/mipmap-mdpi/ic_launcher_round.png b/winboll/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
Binary files /dev/null and b/winboll/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/winboll/src/main/res/mipmap-xhdpi/ic_launcher.png b/winboll/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..dcd3cd8
Binary files /dev/null and b/winboll/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/winboll/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/winboll/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
Binary files /dev/null and b/winboll/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/winboll/src/main/res/mipmap-xxhdpi/ic_launcher.png b/winboll/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8ca12fe
Binary files /dev/null and b/winboll/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/winboll/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/winboll/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
Binary files /dev/null and b/winboll/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
Binary files /dev/null and b/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
Binary files /dev/null and b/winboll/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/winboll/src/main/res/values-zh/strings.xml b/winboll/src/main/res/values-zh/strings.xml
new file mode 100644
index 0000000..045e125
--- /dev/null
+++ b/winboll/src/main/res/values-zh/strings.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/winboll/src/main/res/values/attrs.xml b/winboll/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..1221d42
--- /dev/null
+++ b/winboll/src/main/res/values/attrs.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/values/colors.xml b/winboll/src/main/res/values/colors.xml
new file mode 100644
index 0000000..479769a
--- /dev/null
+++ b/winboll/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #009688
+ #00796B
+ #FF9800
+
\ No newline at end of file
diff --git a/winboll/src/main/res/values/strings.xml b/winboll/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c967735
--- /dev/null
+++ b/winboll/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+ WinBoLL
+ 筋斗云
+ 金抖云
+ WinBoLL
+ 筋斗云
+ 金抖云
+ WinBoLL X
+ 筋斗云 X
+ 金抖云 X
+ WinBoLL
+ WinBoLL APP
+
diff --git a/winboll/src/main/res/values/styles.xml b/winboll/src/main/res/values/styles.xml
new file mode 100644
index 0000000..df38db8
--- /dev/null
+++ b/winboll/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/xml/network_security_config.xml b/winboll/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..ef5fd33
--- /dev/null
+++ b/winboll/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ winboll.cc
+
+
+
+
+ 10.8.0.250
+
+
diff --git a/winboll/src/main/res/xml/shortcutsmaincn1.xml b/winboll/src/main/res/xml/shortcutsmaincn1.xml
new file mode 100644
index 0000000..3196673
--- /dev/null
+++ b/winboll/src/main/res/xml/shortcutsmaincn1.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/xml/shortcutsmaincn2.xml b/winboll/src/main/res/xml/shortcutsmaincn2.xml
new file mode 100644
index 0000000..331ab2e
--- /dev/null
+++ b/winboll/src/main/res/xml/shortcutsmaincn2.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/xml/shortcutsmainen1.xml b/winboll/src/main/res/xml/shortcutsmainen1.xml
new file mode 100644
index 0000000..7d04b71
--- /dev/null
+++ b/winboll/src/main/res/xml/shortcutsmainen1.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/xml/widget_provider_info_sos.xml b/winboll/src/main/res/xml/widget_provider_info_sos.xml
new file mode 100644
index 0000000..e03bd34
--- /dev/null
+++ b/winboll/src/main/res/xml/widget_provider_info_sos.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/winboll/src/main/res/xml/widget_provider_info_status.xml b/winboll/src/main/res/xml/widget_provider_info_status.xml
new file mode 100644
index 0000000..5e19984
--- /dev/null
+++ b/winboll/src/main/res/xml/widget_provider_info_status.xml
@@ -0,0 +1,8 @@
+
+
+