diff --git a/build.gradle b/build.gradle
index 67994863..4d8b47fa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,7 +25,14 @@ buildscript {
//mavenLocal()
// WinBoLL.CC 紧急备用 Maven 仓库
- maven { url 'https://spare-maven.winboll.cc/repository/' }
+ //maven { url 'https://spare-maven.winboll.cc/repository/' }
+ maven {
+ url 'sftp://studio@winboll.cc:2222//winboll/spare-maven/repository/'
+ credentials {
+ username 'studio'
+ password 'winbollstudio'
+ }
+ }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1' // 对应 compileSdkVersion 32
@@ -40,7 +47,6 @@ allprojects {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
}
-
// Nexus Maven 库地址
// "WinBoLL Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
@@ -61,7 +67,14 @@ allprojects {
//mavenLocal()
// WinBoLL.CC 紧急备用 Maven 仓库
- maven { url 'https://spare-maven.winboll.cc/repository/' }
+ //maven { url 'https://spare-maven.winboll.cc/repository/' }
+ maven {
+ url 'sftp://studio@winboll.cc:2222//winboll/spare-maven/repository/'
+ credentials {
+ username 'studio'
+ password 'winbollstudio'
+ }
+ }
}
ext {
// 定义全局变量,常用于版本管理
diff --git a/settings.gradle-demo b/settings.gradle-demo
index c2084594..dea105fa 100644
--- a/settings.gradle-demo
+++ b/settings.gradle-demo
@@ -50,6 +50,7 @@
//include ':androidxdemo'
//rootProject.name = "androidxdemo"
+<<<<<<< HEAD
// Ollama 项目编译设置
//include ':ollama'
//rootProject.name = "ollama"
@@ -64,4 +65,10 @@
// WebPageSources 项目编译设置
//include ':webpagesources'
-//rootProject.name = "webpagesources"
\ No newline at end of file
+//rootProject.name = "webpagesources"
+=======
+
+// WinBoLL 项目编译设置
+//include ':winboll'
+//rootProject.name = "winboll"
+>>>>>>> refs/heads/winboll
diff --git a/winboll/build.gradle b/winboll/build.gradle
new file mode 100644
index 00000000..923a34c5
--- /dev/null
+++ b/winboll/build.gradle
@@ -0,0 +1,73 @@
+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'])
+
+ // 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.8.0'
+ api 'cc.winboll.studio:libapputils:15.8.2'
+ api 'cc.winboll.studio:libappbase:15.8.2'
+}
diff --git a/winboll/build.properties b/winboll/build.properties
new file mode 100644
index 00000000..5fd2d301
--- /dev/null
+++ b/winboll/build.properties
@@ -0,0 +1,8 @@
+#Created by .winboll/winboll_app_build.gradle
+#Thu May 22 13:37:34 HKT 2025
+stageCount=3
+libraryProject=
+baseVersion=15.0
+publishVersion=15.0.2
+buildCount=0
+baseBetaVersion=15.0.3
diff --git a/winboll/proguard-rules.pro b/winboll/proguard-rules.pro
new file mode 100644
index 00000000..64b4a059
--- /dev/null
+++ b/winboll/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/winboll/src/beta/AndroidManifest.xml b/winboll/src/beta/AndroidManifest.xml
new file mode 100644
index 00000000..be352251
--- /dev/null
+++ b/winboll/src/beta/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/beta/res/values/strings.xml b/winboll/src/beta/res/values/strings.xml
new file mode 100644
index 00000000..cec044d9
--- /dev/null
+++ b/winboll/src/beta/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ WinBoLL+
+
+
diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a098c3ce
--- /dev/null
+++ b/winboll/src/main/AndroidManifest.xml
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/App.java b/winboll/src/main/java/cc/winboll/studio/winboll/App.java
new file mode 100644
index 00000000..61b240d6
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/App.java
@@ -0,0 +1,345 @@
+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.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 {
+
+ private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // 初始化 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/CustomToolbar.java b/winboll/src/main/java/cc/winboll/studio/winboll/CustomToolbar.java
new file mode 100644
index 00000000..d9d255ba
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/CustomToolbar.java
@@ -0,0 +1,53 @@
+package cc.winboll.studio.winboll;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/05/22 13:08
+ */
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.Toolbar;
+
+public class CustomToolbar extends Toolbar {
+
+ private View viewMain;
+
+ public CustomToolbar(Context context) {
+ this(context, null);
+ }
+
+ public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView(context, attrs);
+ }
+
+ private void initView(Context context, AttributeSet attrs) {
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
+
+ // 获取属性值
+ String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
+ int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
+ int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
+
+ // 加载布局
+ viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
+
+ // 应用属性值
+ TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
+ toolbarTitleTextView.setText(toolbarTitle);
+ toolbarTitleTextView.setTextColor(toolbarTitleColor);
+ viewMain.setBackgroundColor(toolbarBackgroundColor);
+
+ // 释放 TypedArray
+ typedArray.recycle();
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java
new file mode 100644
index 00000000..ce829c86
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java
@@ -0,0 +1,51 @@
+package cc.winboll.studio.winboll;
+
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import cc.winboll.studio.libappbase.LogView;
+
+public class MainActivity extends AppCompatActivity {
+
+ public static final String TAG = "MainActivity";
+
+ LogView mLogView;
+ private TerminalView mTerminalView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ // 先设置 Toolbar 为支持 ActionBar
+ setSupportActionBar(toolbar);
+
+ // 为导航图标设置点击事件
+ toolbar.setNavigationOnClickListener(new android.view.View.OnClickListener() {
+ @Override
+ public void onClick(android.view.View v) {
+ // 这里可以添加点击事件的逻辑
+ }
+ });
+
+ //getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ //getSupportActionBar().setHomeButtonEnabled(true);
+
+ // 如果要设置其他图标(如菜单图标等),可根据具体需求进行设置
+ // 例如,有些情况下可以通过 MenuInflater 来 inflate 菜单资源,然后在菜单的 onMenuItemClick 中处理图标点击事件
+
+ mLogView = findViewById(R.id.logview);
+
+ mTerminalView = findViewById(R.id.terminalView);
+
+ // 示例:模拟用户输入 "ls" 命令
+ mTerminalView.sendInput("ls");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLogView.start();
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java b/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java
new file mode 100644
index 00000000..9e174108
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java
@@ -0,0 +1,79 @@
+package cc.winboll.studio.appbase;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/13 19:30:10
+ */
+import android.content.Context;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+import cc.winboll.studio.appbase.models.MainServiceBean;
+import cc.winboll.studio.appbase.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/activities/LogonActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java
new file mode 100644
index 00000000..029b1eee
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java
@@ -0,0 +1,150 @@
+package cc.winboll.studio.libappbase.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.RadioButton;
+import cc.winboll.studio.libappbase.BuildConfig;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.LogView;
+import cc.winboll.studio.libappbase.R;
+import cc.winboll.studio.libappbase.models.UserInfoModel;
+import cc.winboll.studio.libappbase.utils.RSAUtils;
+import cc.winboll.studio.libappbase.utils.YunUtils;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/06/04 13:29
+ * @Describe 用户登录框
+ */
+public class LogonActivity extends Activity implements IWinBoLLActivity {
+
+ public static final String TAG = "LogonActivity";
+
+ public static final String DEBUG_HOST = "http://10.8.0.250:456";
+ public static final String YUN_HOST = "https://yun.winboll.cc";
+
+
+ String mHost = "";
+ RadioButton mrbYunHost;
+ RadioButton mrbDebugHost;
+ LogView mLogView;
+
+ @Override
+ public Activity getActivity() {
+ return this;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logon);
+ mLogView = findViewById(R.id.logview);
+ mLogView.start();
+
+ mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
+ if (BuildConfig.DEBUG) {
+ mrbYunHost = findViewById(R.id.rb_yunhost);
+ mrbDebugHost = findViewById(R.id.rb_debughost);
+ mrbYunHost.setChecked(!BuildConfig.DEBUG);
+ mrbDebugHost.setChecked(BuildConfig.DEBUG);
+ } else {
+ findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
+ }
+ }
+
+ public void onSwitchHost(View view) {
+ if (view.getId() == R.id.rb_yunhost) {
+ mrbDebugHost.setChecked(false);
+ mHost = YUN_HOST;
+ } else if (view.getId() == R.id.rb_debughost) {
+ mrbYunHost.setChecked(false);
+ mHost = DEBUG_HOST;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLogView.start();
+ }
+
+ public void onTestLogin(View view) {
+ LogUtils.d(TAG, "onTestLogin");
+ final YunUtils yunUtils = YunUtils.getInstance(this);
+
+ UserInfoModel userInfoModel = new UserInfoModel();
+ userInfoModel.setUsername("jian");
+ userInfoModel.setPassword("kkiio");
+ userInfoModel.setToken("aaa111");
+ yunUtils.login(mHost, userInfoModel);
+ }
+
+ public void onTestRSA(View view) {
+ LogUtils.d(TAG, "onTestRSA");
+ RSAUtils utils = RSAUtils.getInstance(this);
+
+ try {
+ // 测试 1:首次生成密钥对
+ LogUtils.d(TAG, "==== 首次生成密钥对 ====");
+ if (utils.keysExist()) {
+ LogUtils.d(TAG, "密钥对已生成");
+ } else {
+ utils.generateAndSaveKeys();
+ LogUtils.d(TAG, "密钥对生成成功。");
+ }
+
+ // 测试 2:获取密钥对(自动读取已生成的文件)
+ KeyPair keyPair = utils.getOrGenerateKeys();
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ // 打印密钥信息
+ LogUtils.d(TAG, "\n==== 密钥信息 ====");
+ LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
+ LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
+ LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
+ LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
+
+ // 测试 3:重复调用时检查是否复用文件
+ LogUtils.d(TAG, "\n==== 二次调用 ====");
+ KeyPair reusedPair = utils.getOrGenerateKeys();
+ LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用)
+ LogUtils.d(TAG, "操作完成");
+
+ String testMessage = "Hello, RSA Encryption!";
+
+ // 1. 获取或生成密钥对
+ PublicKey publicKeyReused = reusedPair.getPublic();
+ PrivateKey privateKeyReused = reusedPair.getPrivate();
+
+ // 2. 公钥加密
+ byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
+ LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
+
+ // 3. 私钥解密
+ String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
+ LogUtils.d(TAG, "解密结果: " + decryptedMessage);
+
+ // 4. 验证解密是否成功
+ if (testMessage.equals(decryptedMessage)) {
+ LogUtils.d(TAG, "加密解密测试通过!");
+ } else {
+ LogUtils.d(TAG, "测试失败:内容不一致");
+ }
+ } catch (Exception e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+
+
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java
new file mode 100644
index 00000000..faf79d20
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java
@@ -0,0 +1,77 @@
+package cc.winboll.studio.appbase.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.appbase.R;
+import cc.winboll.studio.libappbase.GlobalApplication;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+
+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) {
+ GlobalApplication.getWinBoLLActivityManager().finish(this);
+ }
+
+ public void onCloseAllActivity(View view) {
+ GlobalApplication.getWinBoLLActivityManager().finishAll();
+ }
+
+ public void onNewActivity(View view) {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.toolbar_main, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java
new file mode 100644
index 00000000..f87156e1
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java
@@ -0,0 +1,76 @@
+package cc.winboll.studio.appbase.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.appbase.R;
+import cc.winboll.studio.libappbase.GlobalApplication;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+
+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) {
+ GlobalApplication.getWinBoLLActivityManager().finish(this);
+ }
+
+ public void onCloseAllActivity(View view) {
+ GlobalApplication.getWinBoLLActivityManager().finishAll();
+ }
+
+ public void onNew2Activity(View view) {
+ GlobalApplication.getWinBoLLActivityManager().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/WinBoLLActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java
new file mode 100644
index 00000000..0721c401
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java
@@ -0,0 +1,61 @@
+package cc.winboll.studio.appbase.activities;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/05/10 09:48
+ * @Describe WinBoLL 窗口基础类
+ */
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MenuItem;
+import cc.winboll.studio.appbase.MainActivity;
+import cc.winboll.studio.appbase.R;
+import cc.winboll.studio.libappbase.GlobalApplication;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+
+public class WinBoLLActivity extends Activity 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) {
+ GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
+ return true;
+ } else if (item.getItemId() == R.id.item_home) {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
+ return true;
+ }
+ // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ GlobalApplication.getWinBoLLActivityManager().add(this);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java
new file mode 100644
index 00000000..e40f48fa
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java
@@ -0,0 +1,164 @@
+package cc.winboll.studio.appbase;
+
+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 android.widget.Toolbar;
+import cc.winboll.studio.appbase.R;
+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.utils.ToastUtils;
+
+public class WinBoLLUnitTestActivity extends Activity {
+
+ public static final String TAG = "MainActivity";
+
+ Toolbar mToolbar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ToastUtils.show("onCreate");
+ setContentView(R.layout.activity_main);
+
+ mToolbar = findViewById(R.id.toolbar);
+ setActionBar(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) {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
+ } else if(item.getItemId() == R.id.item_logon) {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.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();
+ }
+
+ 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) {
+ GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java
new file mode 100644
index 00000000..fe929f4c
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java
@@ -0,0 +1,126 @@
+package cc.winboll.studio.libappbase.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import cc.winboll.studio.libappbase.BuildConfig;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.R;
+import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
+import java.io.IOException;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import android.widget.RadioButton;
+import cc.winboll.studio.libappbase.LogView;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/06/04 11:06
+ * @Describe 云宝云
+ */
+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/handlers/MainServiceHandler.java b/winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java
new file mode 100644
index 00000000..c9d6a0e8
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java
@@ -0,0 +1,38 @@
+package cc.winboll.studio.appbase.handlers;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/14 03:51:40
+ */
+import android.os.Handler;
+import android.os.Message;
+import cc.winboll.studio.appbase.services.MainService;
+import java.lang.ref.WeakReference;
+
+public class MainServiceHandler extends Handler {
+ public static final String TAG = "MainServiceHandler";
+
+ public static final int MSG_REMINDTHREAD = 0;
+
+ WeakReference serviceWeakReference;
+ public MainServiceHandler(MainService service) {
+ serviceWeakReference = new WeakReference(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI
+ {
+ // 显示提醒消息
+ //
+ //LogUtils.d(TAG, "显示提醒消息");
+ MainService mainService = serviceWeakReference.get();
+ if (mainService != null) {
+ mainService.appenMessage((String)msg.obj);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java
new file mode 100644
index 00000000..01dc4144
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java
@@ -0,0 +1,67 @@
+package cc.winboll.studio.appbase.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/TestDemoBindServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java
new file mode 100644
index 00000000..75f7184b
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java
@@ -0,0 +1,67 @@
+package cc.winboll.studio.appbase.models;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/03/07 12:47:22
+ * @Describe TestServiceBean
+ */
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+public class TestDemoBindServiceBean extends BaseBean {
+
+ public static final String TAG = "TestServiceBean";
+
+ boolean isEnable;
+
+ public TestDemoBindServiceBean() {
+ this.isEnable = false;
+ }
+
+ public void setIsEnable(boolean isEnable) {
+ this.isEnable = isEnable;
+ }
+
+ public boolean isEnable() {
+ return isEnable;
+ }
+
+ @Override
+ public String getName() {
+ return TestDemoBindServiceBean.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ jsonWriter.name("isEnable").value(isEnable());
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("isEnable")) {
+ setIsEnable(jsonReader.nextBoolean());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ // 结束 JSON 对象
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java
new file mode 100644
index 00000000..8d6a379a
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java
@@ -0,0 +1,68 @@
+package cc.winboll.studio.appbase.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/WinBoLLNewsBean.java b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java
new file mode 100644
index 00000000..de2b03d9
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java
@@ -0,0 +1,70 @@
+package cc.winboll.studio.libappbase.models;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/05/10 09:36
+ * @Describe WinBoLL 应用消息数据模型
+ */
+public class WinBoLLNewsBean extends BaseBean {
+
+ public static final String TAG = "WinBoLLNewsBean";
+
+ String message;
+
+ public WinBoLLNewsBean() {
+ this.message = "";
+ }
+
+ public WinBoLLNewsBean(String message) {
+ this.message = message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String getName() {
+ return WinBoLLNewsBean.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ jsonWriter.name("message").value(getMessage());
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("message")) {
+ setMessage(jsonReader.nextString());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ // 结束 JSON 对象
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java
new file mode 100644
index 00000000..fd4a35d9
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java
@@ -0,0 +1,36 @@
+package cc.winboll.studio.appbase.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.appbase.widgets.APPNewsWidget;
+import cc.winboll.studio.libappbase.LogUtils;
+
+public class APPNewsWidgetClickListener extends BroadcastReceiver {
+
+ public static final String TAG = "APPNewsWidgetClickListener";
+ public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE";
+ public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ LogUtils.d(TAG, String.format("action %s", action));
+ return;
+ }
+ if (action.equals(ACTION_PRE)) {
+ LogUtils.d(TAG, "ACTION_PRE");
+ APPNewsWidget.prePage(context);
+ } else if (action.equals(ACTION_NEXT)) {
+ LogUtils.d(TAG, "ACTION_NEXT");
+ APPNewsWidget.nextPage(context);
+ } else {
+ LogUtils.d(TAG, String.format("action %s", action));
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java
new file mode 100644
index 00000000..ee435a93
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java
@@ -0,0 +1,115 @@
+package cc.winboll.studio.appbase.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.appbase.services.MainService;
+import cc.winboll.studio.appbase.widgets.APPNewsWidget;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.models.WinBoLLModel;
+import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
+import cc.winboll.studio.libappbase.sos.SOS;
+import cc.winboll.studio.libappbase.sos.SOSObject;
+import cc.winboll.studio.libappbase.utils.ToastUtils;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class MainReceiver extends BroadcastReceiver {
+
+ public static final String TAG = "MainReceiver";
+
+ public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
+
+ WeakReference mwrService;
+
+ public MainReceiver(MainService service) {
+ mwrService = new WeakReference(service);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String szAction = intent.getAction();
+ if (szAction.equals(ACTION_BOOT_COMPLETED)) {
+ ToastUtils.show("ACTION_BOOT_COMPLETED");
+ } else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) {
+ LogUtils.d(TAG, "ACTION_BIND");
+ LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
+ LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
+ String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
+ LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel));
+ if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
+ try {
+ WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
+ if (bean != null) {
+ String szAppPackageName = bean.getAppPackageName();
+ LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
+ String szAppMainServiveName = bean.getAppMainServiveName();
+ LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
+ mwrService.get().bindWinBoLLModelConnection(bean);
+ }
+ } catch (IOException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+ } else if (intent.getAction().equals(SOS.ACTION_SOS)) {
+ LogUtils.d(TAG, "ACTION_SOS");
+ String sos = intent.getStringExtra(SOS.EXTRA_OBJECT);
+ LogUtils.d(TAG, String.format("SOS %s", sos));
+ if (sos != null && !sos.equals("")) {
+ SOSObject bean = SOS.parseSOSObject(sos);
+ if (bean != null) {
+ String szObjectPackageName = bean.getObjectPackageName();
+ LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName));
+ String szObjectServiveName = bean.getObjectServiveName();
+ LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName));
+
+ Intent intentService = new Intent();
+ intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName));
+ context.startService(intentService);
+
+ String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName);
+ LogUtils.d(TAG, String.format("appName %s", appName));
+ WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName);
+ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+ String currentTime = sdf.format(new Date());
+ StringBuilder sbLine = new StringBuilder();
+ sbLine.append("[");
+ sbLine.append(currentTime);
+ sbLine.append("] Power to ");
+ sbLine.append(appName);
+ appWinBoLLNewsBean.setMessage(sbLine.toString());
+
+ APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean);
+
+ Intent intentWidget = new Intent(context, APPNewsWidget.class);
+ intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
+ context.sendBroadcast(intentWidget);
+ }
+
+
+ }
+ } else {
+ ToastUtils.show(szAction);
+ }
+ }
+
+ // 注册 Receiver
+ //
+ public void registerAction(MainService service) {
+ IntentFilter filter=new IntentFilter();
+ filter.addAction(ACTION_BOOT_COMPLETED);
+ filter.addAction(SOS.ACTION_SOS);
+ filter.addAction(WinBoLL.ACTION_BIND);
+ //filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ service.registerReceiver(this, filter);
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java
new file mode 100644
index 00000000..bcd970ed
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java
@@ -0,0 +1,29 @@
+package cc.winboll.studio.libappbase.receiver;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/13 21:19:09
+ * @Describe MyBroadcastReceiver
+ */
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.R;
+
+public class MyBroadcastReceiver extends BroadcastReceiver {
+
+ public static final String TAG = "MyBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (context.getString(R.string.action_sos).equals(intent.getAction())) {
+ String message = intent.getStringExtra("message");
+ String sosPackage = intent.getStringExtra("sosPackage");
+
+ // 处理接收到的广播消息
+ LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message));
+ }
+ }
+}
+
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java
new file mode 100644
index 00000000..07702412
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java
@@ -0,0 +1,138 @@
+package cc.winboll.studio.appbase.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.appbase.models.MainServiceBean;
+import cc.winboll.studio.appbase.services.AssistantService;
+import cc.winboll.studio.appbase.services.MainService;
+import cc.winboll.studio.libappbase.LogUtils;
+import android.os.Binder;
+
+public class AssistantService extends Service {
+
+ public static final String TAG = "AssistantService";
+
+ MainServiceBean mMainServiceBean;
+ MyServiceConnection mMyServiceConnection;
+ MainService mMainService;
+ boolean isBound = false;
+ volatile boolean isThreadAlive = false;
+
+ public synchronized void setIsThreadAlive(boolean isThreadAlive) {
+ LogUtils.d(TAG, "setIsThreadAlive(...)");
+ LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive));
+ this.isThreadAlive = isThreadAlive;
+ }
+
+ public boolean isThreadAlive() {
+ return isThreadAlive;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MyBinder();
+ }
+
+ @Override
+ public void onCreate() {
+ LogUtils.d(TAG, "onCreate");
+ super.onCreate();
+
+ //mMyBinder = new MyBinder();
+ if (mMyServiceConnection == null) {
+ mMyServiceConnection = new MyServiceConnection();
+ }
+ // 设置运行参数
+ setIsThreadAlive(false);
+ assistantService();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.d(TAG, "call onStartCommand(...)");
+ assistantService();
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ //LogUtils.d(TAG, "onDestroy");
+ setIsThreadAlive(false);
+ // 解除绑定
+ if (isBound) {
+ unbindService(mMyServiceConnection);
+ isBound = false;
+ }
+ super.onDestroy();
+ }
+
+ // 运行服务内容
+ //
+ void assistantService() {
+ LogUtils.d(TAG, "assistantService()");
+ mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
+ LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable()));
+ if (mMainServiceBean.isEnable()) {
+ LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive()));
+ if (isThreadAlive() == false) {
+ // 设置运行状态
+ setIsThreadAlive(true);
+ // 唤醒和绑定主进程
+ wakeupAndBindMain();
+ }
+ }
+ }
+
+ // 唤醒和绑定主进程
+ //
+ void wakeupAndBindMain() {
+ LogUtils.d(TAG, "wakeupAndBindMain()");
+ // 绑定服务的Intent
+ Intent intent = new Intent(this, MainService.class);
+ startService(new Intent(this, MainService.class));
+ bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
+
+// startService(new Intent(this, MainService.class));
+// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
+ }
+
+ // 主进程与守护进程连接时需要用到此类
+ //
+ class MyServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ LogUtils.d(TAG, "onServiceConnected(...)");
+ MainService.MyBinder binder = (MainService.MyBinder) service;
+ mMainService = binder.getService();
+ isBound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ LogUtils.d(TAG, "onServiceDisconnected(...)");
+ mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class);
+ if (mMainServiceBean.isEnable()) {
+ wakeupAndBindMain();
+ }
+ isBound = false;
+ mMainService = null;
+ }
+ }
+
+ // 用于返回服务实例的Binder
+ public class MyBinder extends Binder {
+ AssistantService getService() {
+ LogUtils.d(TAG, "AssistantService MyBinder getService()");
+ return AssistantService.this;
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java
new file mode 100644
index 00000000..f71bdbb7
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java
@@ -0,0 +1,317 @@
+package cc.winboll.studio.appbase.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.appbase.MyTileService;
+import cc.winboll.studio.appbase.handlers.MainServiceHandler;
+import cc.winboll.studio.appbase.models.MainServiceBean;
+import cc.winboll.studio.appbase.receivers.MainReceiver;
+import cc.winboll.studio.appbase.services.AssistantService;
+import cc.winboll.studio.appbase.threads.MainServiceThread;
+import cc.winboll.studio.appbase.widgets.APPNewsWidget;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.models.APPModel;
+import cc.winboll.studio.libappbase.models.WinBoLLModel;
+import java.util.ArrayList;
+
+public class MainService extends Service {
+
+ public static final String TAG = "MainService";
+
+ public static final int MSG_UPDATE_STATUS = 0;
+
+ static MainService _mControlCenterService;
+
+ volatile boolean isServiceRunning;
+
+ MainServiceBean mMainServiceBean;
+ MainServiceThread mMainServiceThread;
+ MainServiceHandler mMainServiceHandler;
+ MyServiceConnection mMyServiceConnection;
+ AssistantService mAssistantService;
+ boolean isBound = false;
+ MainReceiver mMainReceiver;
+ ArrayList mAPPModelConnectionList;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MyBinder();
+ }
+
+ public MainServiceThread getRemindThread() {
+ return mMainServiceThread;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LogUtils.d(TAG, "onCreate()");
+ mAPPModelConnectionList = new ArrayList();
+
+ _mControlCenterService = MainService.this;
+ isServiceRunning = false;
+ mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
+
+ if (mMyServiceConnection == null) {
+ mMyServiceConnection = new MyServiceConnection();
+ }
+ mMainServiceHandler = new MainServiceHandler(this);
+
+ // 运行服务内容
+ mainService();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.d(TAG, "onStartCommand(...)");
+ // 运行服务内容
+ mainService();
+ return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
+ }
+
+ // 运行服务内容
+ //
+ void mainService() {
+ LogUtils.d(TAG, "mainService()");
+ mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
+ if (mMainServiceBean.isEnable() && isServiceRunning == false) {
+ LogUtils.d(TAG, "mainService() start running");
+ isServiceRunning = true;
+ // 唤醒守护进程
+ wakeupAndBindAssistant();
+
+ if (mMainReceiver == null) {
+ // 注册广播接收器
+ mMainReceiver = new MainReceiver(this);
+ mMainReceiver.registerAction(this);
+ }
+
+ // 启动小部件
+ Intent intentTimeWidget = new Intent(this, APPNewsWidget.class);
+ intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
+ this.sendBroadcast(intentTimeWidget);
+
+ startMainServiceThread();
+
+ MyTileService.updateServiceIconStatus(this);
+
+ LogUtils.i(TAG, "Main Service Is Start.");
+ }
+ }
+
+ // 唤醒和绑定守护进程
+ //
+ void wakeupAndBindAssistant() {
+ LogUtils.d(TAG, "wakeupAndBindAssistant()");
+
+ Intent intent = new Intent(this, AssistantService.class);
+ startService(intent);
+ // 绑定服务的Intent
+ bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
+ }
+
+ // 开启提醒铃声线程
+ //
+ public void startMainServiceThread() {
+ LogUtils.d(TAG, "startMainServiceThread");
+ if (mMainServiceThread == null) {
+ mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
+ LogUtils.d(TAG, "new MainServiceThread");
+ } else {
+ if (mMainServiceThread.isExist() == true) {
+ mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
+ LogUtils.d(TAG, "renew MainServiceThread");
+ } else {
+ // 提醒进程正在进行中就更新状态后退出
+ LogUtils.d(TAG, "A mMainServiceThread running.");
+ return;
+ }
+ }
+ mMainServiceThread.start();
+ }
+
+ public void stopRemindThread() {
+ if (mMainServiceThread != null) {
+ mMainServiceThread.setIsExist(true);
+ mMainServiceThread = null;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ //LogUtils.d(TAG, "onDestroy");
+ mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
+ if (mMainServiceBean.isEnable() == false) {
+ // 设置运行状态
+ isServiceRunning = false;// 解除绑定
+ if (isBound) {
+ unbindService(mMyServiceConnection);
+ isBound = false;
+ }
+ // 停止守护进程
+ Intent intent = new Intent(this, AssistantService.class);
+ stopService(intent);
+ // 停止Receiver
+ if (mMainReceiver != null) {
+ unregisterReceiver(mMainReceiver);
+ mMainReceiver = null;
+ }
+ // 停止前台通知栏
+ stopForeground(true);
+ // 停止消息提醒进程
+ stopRemindThread();
+
+ MyTileService.updateServiceIconStatus(this);
+
+ super.onDestroy();
+ //LogUtils.d(TAG, "onDestroy done");
+ }
+ }
+
+ public void bindWinBoLLModelConnection(WinBoLLModel bean) {
+ LogUtils.d(TAG, "bindAPPModelConnection(...)");
+ // 清理旧的绑定链接
+ for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) {
+ APPConnection item = mAPPModelConnectionList.get(i);
+ if (item.isBindToAPP(bean)) {
+ LogUtils.d(TAG, "Bind Servive exist.");
+ unbindService(item);
+ mAPPModelConnectionList.remove(i);
+ }
+ }
+
+ // 绑定服务
+ APPConnection appConnection = new APPConnection();
+ Intent intentService = new Intent();
+ intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName()));
+ bindService(intentService, appConnection, Context.BIND_IMPORTANT);
+ mAPPModelConnectionList.add(appConnection);
+
+ Intent intentWidget = new Intent(this, APPNewsWidget.class);
+ intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE);
+ WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName());
+ intentWidget.putExtra("APPSOSBean", appSOSBean.toString());
+ sendBroadcast(intentWidget);
+ }
+
+ public class APPConnection implements ServiceConnection {
+
+ ComponentName mComponentName;
+
+ boolean isBindToAPP(WinBoLLModel bean) {
+ return mComponentName != null
+ && mComponentName.getClassName().equals(bean.getAppMainServiveName())
+ && mComponentName.getPackageName().equals(bean.getAppPackageName());
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ LogUtils.d(TAG, "onServiceConnected(...)");
+ mComponentName = name;
+ LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ LogUtils.d(TAG, "onServiceDisconnected(...)");
+ LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
+
+ // 尝试无参数启动一下服务
+ String appPackage = mComponentName.getPackageName();
+ LogUtils.d(TAG, String.format("appPackage %s", appPackage));
+ String appMainServiceClassName = mComponentName.getClassName();
+ LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName));
+
+ Intent intentService = new Intent();
+ intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName));
+ startService(intentService);
+ }
+
+ }
+
+ // 主进程与守护进程连接时需要用到此类
+ //
+ private class MyServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ LogUtils.d(TAG, "onServiceConnected(...)");
+ AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
+ mAssistantService = binder.getService();
+ isBound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ LogUtils.d(TAG, "onServiceDisconnected(...)");
+
+ if (mMainServiceBean.isEnable()) {
+ // 唤醒守护进程
+ wakeupAndBindAssistant();
+ }
+ isBound = false;
+ mAssistantService = null;
+ }
+
+ }
+
+
+ // 用于返回服务实例的Binder
+ public class MyBinder extends Binder {
+ MainService getService() {
+ LogUtils.d(TAG, "MainService MyBinder getService()");
+ return MainService.this;
+ }
+ }
+
+// //
+// // 启动服务
+// //
+// public static void startControlCenterService(Context context) {
+// Intent intent = new Intent(context, MainService.class);
+// context.startForegroundService(intent);
+// }
+//
+// //
+// // 停止服务
+// //
+// public static void stopControlCenterService(Context context) {
+// Intent intent = new Intent(context, MainService.class);
+// context.stopService(intent);
+// }
+
+ public void appenMessage(String message) {
+ LogUtils.d(TAG, String.format("Message : %s", message));
+ }
+
+ public static void stopMainService(Context context) {
+ LogUtils.d(TAG, "stopMainService");
+ MainServiceBean bean = new MainServiceBean();
+ bean.setIsEnable(false);
+ MainServiceBean.saveBean(context, bean);
+ context.stopService(new Intent(context, MainService.class));
+ }
+
+ public static void startMainService(Context context) {
+ LogUtils.d(TAG, "startMainService");
+ MainServiceBean bean = new MainServiceBean();
+ bean.setIsEnable(true);
+ MainServiceBean.saveBean(context, bean);
+ context.startService(new Intent(context, MainService.class));
+ }
+}
+
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java
new file mode 100644
index 00000000..7136d3ce
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java
@@ -0,0 +1,179 @@
+package cc.winboll.studio.appbase.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.appbase.App;
+import cc.winboll.studio.appbase.models.TestDemoBindServiceBean;
+import cc.winboll.studio.appbase.services.TestDemoBindService;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.sos.SOS;
+import cc.winboll.studio.libappbase.winboll.WinBoLL;
+
+public class TestDemoBindService extends Service {
+
+ public static final String TAG = "TestDemoBindService";
+
+ public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE";
+ public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE";
+
+ volatile static TestThread _TestThread;
+
+ volatile static boolean _IsRunning;
+
+ public synchronized static void setIsRunning(boolean isRunning) {
+ _IsRunning = isRunning;
+ }
+
+ public static boolean isRunning() {
+ return _IsRunning;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MyBinder();
+ }
+
+ public class MyBinder extends Binder {
+ public TestDemoBindService getService() {
+ return TestDemoBindService.this;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LogUtils.d(TAG, "onCreate()");
+
+ run();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.d(TAG, "onStartCommand(...)");
+ TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
+ if (bean == null) {
+ bean = new TestDemoBindServiceBean();
+ }
+
+ if (intent.getAction() != null) {
+ if (intent.getAction().equals(ACTION_ENABLE)) {
+ bean.setIsEnable(true);
+ LogUtils.d(TAG, "setIsEnable(true);");
+ TestDemoBindServiceBean.saveBean(this, bean);
+ } else if (intent.getAction().equals(ACTION_DISABLE)) {
+ bean.setIsEnable(false);
+ LogUtils.d(TAG, "setIsEnable(false);");
+ TestDemoBindServiceBean.saveBean(this, bean);
+ }
+ }
+
+ run();
+
+ return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
+ //return super.onStartCommand(intent, flags, startId);
+ }
+
+ void run() {
+ LogUtils.d(TAG, "run()");
+ TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
+ if (bean == null) {
+ bean = new TestDemoBindServiceBean();
+ TestDemoBindServiceBean.saveBean(this, bean);
+ }
+ if (bean.isEnable()) {
+ LogUtils.d(TAG, "run() bean.isEnable()");
+ TestThread.getInstance(this).start();
+ LogUtils.d(TAG, "_TestThread.start()");
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LogUtils.d(TAG, "onDestroy()");
+ TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
+ if (bean == null) {
+ bean = new TestDemoBindServiceBean();
+ }
+
+ TestThread.getInstance(this).setIsExit(true);
+
+ // 预防 APPBase 应用重启绑定失效。
+ // 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。
+ // 这样 APPBase 就会用组件方式启动本服务。
+ if (bean.isEnable()) {
+ if (App.isDebuging()) {
+ SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName());
+ } else {
+ SOS.sosToAppBase(this, TestDemoBindService.class.getName());
+ }
+ }
+
+ _IsRunning = false;
+ }
+
+ static class TestThread extends Thread {
+
+ volatile static TestThread _TestThread;
+ Context mContext;
+ volatile boolean isStarted = false;
+ volatile boolean isExit = false;
+
+ TestThread(Context context) {
+ super();
+ mContext = context;
+ }
+
+ public static synchronized TestThread getInstance(Context context) {
+ if (_TestThread != null) {
+ _TestThread.setIsExit(true);
+ }
+ _TestThread = new TestThread(context);
+
+ return _TestThread;
+ }
+
+ public synchronized void setIsExit(boolean isExit) {
+ this.isExit = isExit;
+ }
+
+ public boolean isExit() {
+ return isExit;
+ }
+
+ @Override
+ public void run() {
+ if (isStarted == false) {
+ isStarted = true;
+ super.run();
+ LogUtils.d(TAG, "run() start");
+ if (App.isDebuging()) {
+ WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName());
+ } else {
+ WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName());
+ }
+
+ while (!isExit()) {
+ LogUtils.d(TAG, "run()");
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+
+ LogUtils.d(TAG, "run() exit");
+ }
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java
new file mode 100644
index 00000000..ee7de32d
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java
@@ -0,0 +1,155 @@
+package cc.winboll.studio.appbase.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.appbase.models.TestDemoServiceBean;
+import cc.winboll.studio.libappbase.LogUtils;
+
+public class TestDemoService extends Service {
+
+ public static final String TAG = "TestDemoService";
+
+ public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE";
+ public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE";
+
+ volatile static TestThread _TestThread;
+
+ volatile static boolean _IsRunning;
+
+ public synchronized static void setIsRunning(boolean isRunning) {
+ _IsRunning = isRunning;
+ }
+
+ public static boolean isRunning() {
+ return _IsRunning;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MyBinder();
+ }
+
+ public class MyBinder extends Binder {
+ public TestDemoService getService() {
+ return TestDemoService.this;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LogUtils.d(TAG, "onCreate()");
+
+
+ run();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.d(TAG, "onStartCommand(...)");
+ TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
+ if (bean == null) {
+ bean = new TestDemoServiceBean();
+ }
+
+ if (intent.getAction() != null) {
+ if (intent.getAction().equals(ACTION_ENABLE)) {
+ bean.setIsEnable(true);
+ LogUtils.d(TAG, "setIsEnable(true);");
+ TestDemoServiceBean.saveBean(this, bean);
+ } else if (intent.getAction().equals(ACTION_DISABLE)) {
+ bean.setIsEnable(false);
+ LogUtils.d(TAG, "setIsEnable(false);");
+ TestDemoServiceBean.saveBean(this, bean);
+ }
+ }
+
+ run();
+
+ return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
+ //return super.onStartCommand(intent, flags, startId);
+ }
+
+ void run() {
+ LogUtils.d(TAG, "run()");
+ TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
+ if (bean == null) {
+ bean = new TestDemoServiceBean();
+ TestDemoServiceBean.saveBean(this, bean);
+ }
+ if (bean.isEnable()) {
+ LogUtils.d(TAG, "run() bean.isEnable()");
+ TestThread.getInstance(this).start();
+ LogUtils.d(TAG, "_TestThread.start()");
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LogUtils.d(TAG, "onDestroy()");
+ TestThread.getInstance(this).setIsExit(true);
+
+ _IsRunning = false;
+ }
+
+ static class TestThread extends Thread {
+
+ volatile static TestThread _TestThread;
+ Context mContext;
+ volatile boolean isStarted = false;
+ volatile boolean isExit = false;
+
+ TestThread(Context context) {
+ super();
+ mContext = context;
+ }
+
+ public static synchronized TestThread getInstance(Context context) {
+ if (_TestThread != null) {
+ _TestThread.setIsExit(true);
+ }
+ _TestThread = new TestThread(context);
+
+ return _TestThread;
+ }
+
+ public synchronized void setIsExit(boolean isExit) {
+ this.isExit = isExit;
+ }
+
+ public boolean isExit() {
+ return isExit;
+ }
+
+ @Override
+ public void run() {
+ if (isStarted == false) {
+ isStarted = true;
+ super.run();
+ LogUtils.d(TAG, "run() start");
+
+ while (!isExit()) {
+ LogUtils.d(TAG, "run()");
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+
+ LogUtils.d(TAG, "run() exit");
+ }
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java
new file mode 100644
index 00000000..6d80d255
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java
@@ -0,0 +1,59 @@
+package cc.winboll.studio.libappbase.sos;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/03/02 09:36:29
+ * @Describe WinBoLL 应用 SOS 机理保护类
+ */
+import android.content.Context;
+import android.content.Intent;
+import cc.winboll.studio.libappbase.LogUtils;
+import java.io.IOException;
+
+public class SOS {
+
+ public static final String TAG = "SOS";
+
+ public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS";
+ public static final String EXTRA_OBJECT = "EXTRA_OBJECT";
+
+ public static void sosToAppBase(Context context, String sosService) {
+ LogUtils.d(TAG, "sosToAppBase()");
+ String szToPackage = "cc.winboll.studio.appbase";
+ sos(context, szToPackage, sosService);
+
+ }
+
+ public static void sosToAppBaseBeta(Context context, String sosService) {
+ LogUtils.d(TAG, "sosToAppBaseBeta()");
+ String szToPackage = "cc.winboll.studio.appbase.beta";
+ sos(context, szToPackage, sosService);
+
+ }
+
+ static void sos(Context context, String szToPackage, String sosService) {
+ LogUtils.d(TAG, "sos(...)");
+ Intent intent = new Intent(ACTION_SOS);
+ intent.putExtra(EXTRA_OBJECT, genSOSObject(context.getPackageName(), sosService));
+ intent.setPackage(szToPackage);
+ LogUtils.d(TAG, String.format("ACTION_SOS :\nTo Package : %sSOS Service : %s\n", szToPackage, sosService));
+ context.sendBroadcast(intent);
+ }
+
+ public static SOSObject parseSOSObject(String szSOSObject) {
+ try {
+ return SOSObject.parseStringToBean(szSOSObject, SOSObject.class);
+ } catch (IOException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ return null;
+ }
+
+ public static String sosObjectToString(SOSObject object) {
+ return object.toString();
+ }
+
+ public static String genSOSObject(String objectPackageName, String objectServiveName) {
+ return (new SOSObject(objectPackageName, objectServiveName)).toString();
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java
new file mode 100644
index 00000000..7c7c1fa7
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java
@@ -0,0 +1,182 @@
+package cc.winboll.studio.libappbase.sos;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/27 14:00:21
+ * @Describe Simple Operate Signal Service Center.
+ * 简单操作信号服务中心
+ */
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+import java.io.FileDescriptor;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import cc.winboll.studio.libappbase.LogUtils;
+
+public class SOSCenterService extends Service {
+
+ public static final String TAG = "SOSCenterService";
+
+ private final IBinder binder =(IBinder)new SOSBinder();
+
+ SOSCenterServiceModel mSOSCenterServiceModel;
+ static MainThread _MainThread;
+ public static synchronized MainThread getMainThreadInstance() {
+ if (_MainThread == null) {
+ _MainThread = new MainThread();
+ }
+ return _MainThread;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ public class SOSBinder implements IBinder {
+
+ @Override
+ public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
+ }
+
+ @Override
+ public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
+ }
+
+ @Override
+ public String getInterfaceDescriptor() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean isBinderAlive() {
+ return false;
+ }
+
+ @Override
+ public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException {
+ }
+
+ @Override
+ public boolean pingBinder() {
+ return false;
+ }
+
+ @Override
+ public IInterface queryLocalInterface(String string) {
+ return null;
+ }
+
+ @Override
+ public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) {
+ return false;
+ }
+
+ public static final String TAG = "SOSBinder";
+ SOSCenterService getService() {
+ return SOSCenterService.this;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LogUtils.d(TAG, "onCreate");
+ mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
+ if(mSOSCenterServiceModel == null) {
+ mSOSCenterServiceModel = new SOSCenterServiceModel();
+ SOSCenterServiceModel.saveBean(this, mSOSCenterServiceModel);
+ }
+ runMainThread();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.d(TAG, "onStartCommand");
+
+ runMainThread();
+
+ return mSOSCenterServiceModel.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
+ }
+
+ void runMainThread() {
+ mSOSCenterServiceModel = mSOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
+ if (mSOSCenterServiceModel.isEnable()
+ && _MainThread == null) {
+ getMainThreadInstance().start();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LogUtils.d(TAG, "onDestroy");
+ mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
+ if (mSOSCenterServiceModel.isEnable()) {
+ LogUtils.d(TAG, "mSOSCenterServiceModel.isEnable()");
+// ISOSAPP iSOSAPP = (ISOSAPP)getApplication();
+// iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp());
+ }
+ if (_MainThread != null) {
+ _MainThread.isExist = true;
+ _MainThread = null;
+ }
+ }
+
+ public static void stopISOSService(Context context) {
+ LogUtils.d(TAG, "stopISOSService");
+ SOSCenterServiceModel bean = new SOSCenterServiceModel();
+ bean.setIsEnable(false);
+ SOSCenterServiceModel.saveBean(context, bean);
+ context.stopService(new Intent(context, SOSCenterServiceModel.class));
+ }
+
+ public static void startISOSService(Context context) {
+ LogUtils.d(TAG, "startISOSService");
+ SOSCenterServiceModel bean = new SOSCenterServiceModel();
+ bean.setIsEnable(true);
+ SOSCenterServiceModel.saveBean(context, bean);
+ context.startService(new Intent(context, SOSCenterServiceModel.class));
+ }
+
+ public String getMessage() {
+ return "Hello from SOSCenterServiceModel";
+ }
+
+ static class MainThread extends Thread {
+ volatile boolean isExist = false;
+
+ public void setIsExist(boolean isExist) {
+ this.isExist = isExist;
+ }
+
+ public boolean isExist() {
+ return isExist;
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ while (!isExist) {
+ LogUtils.d(TAG, "run");
+ try {
+ sleep(1000);
+ } catch (InterruptedException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+ }
+
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java
new file mode 100644
index 00000000..f2cfc401
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java
@@ -0,0 +1,69 @@
+package cc.winboll.studio.libappbase.sos;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/03/02 09:49:45
+ * @Describe SOSCenterServiceModel
+ * Simple Operate Signal Service Model.
+ */
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+public class SOSCenterServiceModel extends BaseBean {
+
+ public static final String TAG = "SOSCenterServiceModel";
+
+ boolean isEnable;
+
+ public SOSCenterServiceModel() {
+ this.isEnable = false;
+ }
+
+ public void setIsEnable(boolean isEnable) {
+ this.isEnable = isEnable;
+ }
+
+ public boolean isEnable() {
+ return isEnable;
+ }
+
+ @Override
+ public String getName() {
+ return SOSCenterServiceModel.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ jsonWriter.name("isEnable").value(isEnable());
+
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("isEnable")) {
+ setIsEnable(jsonReader.nextBoolean());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ // 结束 JSON 对象
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java
new file mode 100644
index 00000000..ec3341b1
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java
@@ -0,0 +1,29 @@
+package cc.winboll.studio.libappbase.sos;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/27 14:04:35
+ * @Describe SOSCenterServiceReceiver
+ */
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import cc.winboll.studio.libappbase.LogUtils;
+
+public class SOSCenterServiceReceiver extends BroadcastReceiver {
+
+ public static final String TAG = "SOSCenterServiceReceiver";
+
+ public static final String ACTION_SOS = SOSCenterServiceReceiver.class.getName() + ".ACTION_SOS";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_SOS)) {
+ // 处理接收到的广播消息
+ LogUtils.d(TAG, String.format("Action %s \n%s\n%s", action));
+ } else {
+ LogUtils.d(TAG, String.format("%s", action));
+ }
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java
new file mode 100644
index 00000000..5f19563a
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java
@@ -0,0 +1,86 @@
+package cc.winboll.studio.libappbase.sos;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/27 14:12:05
+ * @Describe SOSBean
+ */
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import cc.winboll.studio.libappbase.BaseBean;
+import java.io.IOException;
+
+public class SOSObject extends BaseBean {
+
+ public static final String TAG = "SOSObject";
+
+ String objectPackageName;
+ String objectServiveName;
+
+ public SOSObject() {
+ this.objectPackageName = "";
+ this.objectServiveName = "";
+ }
+
+ public SOSObject(String objectPackageName, String objectServiveName) {
+ this.objectPackageName = objectPackageName;
+ this.objectServiveName = objectServiveName;
+ }
+
+ public void setObjectPackageName(String objectPackageName) {
+ this.objectPackageName = objectPackageName;
+ }
+
+ public String getObjectPackageName() {
+ return objectPackageName;
+ }
+
+ public void setObjectServiveName(String objectServiveName) {
+ this.objectServiveName = objectServiveName;
+ }
+
+ public String getObjectServiveName() {
+ return objectServiveName;
+ }
+
+ @Override
+ public String getName() {
+ return SOSObject.class.getName();
+ }
+
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ jsonWriter.name("objectPackageName").value(getObjectPackageName());
+ jsonWriter.name("objectServiveName").value(getObjectServiveName());
+
+ }
+
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
+ if (name.equals("objectPackageName")) {
+ setObjectPackageName(jsonReader.nextString());
+ } else if (name.equals("objectServiveName")) {
+ setObjectServiveName(jsonReader.nextString());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue();
+ }
+ }
+ // 结束 JSON 对象
+ jsonReader.endObject();
+ return this;
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java b/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java
new file mode 100644
index 00000000..c922e84a
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java
@@ -0,0 +1,54 @@
+package cc.winboll.studio.appbase.threads;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/02/14 03:46:44
+ */
+import android.content.Context;
+import cc.winboll.studio.appbase.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/TermuxUtils.java b/winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java
new file mode 100644
index 00000000..ad7a72d9
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java
@@ -0,0 +1,31 @@
+package cc.winboll.studio.winboll.utils;
+
+/**
+ * @Author ZhanGSKen
+ * @Date 2025/06/08 09:05
+ * @Describe Termux 应用操作工具集
+ */
+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/widgets/APPNewsWidget.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java
new file mode 100644
index 00000000..71a5caa1
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java
@@ -0,0 +1,187 @@
+package cc.winboll.studio.appbase.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.appbase.R;
+import cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener;
+import cc.winboll.studio.libappbase.AppUtils;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.models.APPModel;
+import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
+import cc.winboll.studio.libappbase.winboll.WinBoLL;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import cc.winboll.studio.libappbase.models.WinBoLLModel;
+
+public class APPNewsWidget extends AppWidgetProvider {
+
+ public static final String TAG = "APPNewsWidget";
+
+ public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE";
+ public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT";
+
+
+ volatile static ArrayList _WinBoLLNewsBeanList;
+ final static int _MAX_PAGES = 10;
+ final static int _OnePageLinesCount = 5;
+ volatile static int _CurrentPageIndex = 0;
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ initWinBoLLNewsBeanList(context);
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ initWinBoLLNewsBeanList(context);
+ if (intent.getAction().equals(ACTION_RELOAD_REPORT)) {
+ LogUtils.d(TAG, "ACTION_RELOAD_REPORT");
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) {
+ LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE");
+ String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
+ LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel));
+ if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
+ try {
+ WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
+ if (bean != null) {
+ String szAppPackageName = bean.getAppPackageName();
+ LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
+ String szAppMainServiveName = bean.getAppMainServiveName();
+ LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
+
+
+ String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName);
+ LogUtils.d(TAG, String.format("appName %s", appName));
+ WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName);
+ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+ String currentTime = sdf.format(new Date());
+ StringBuilder sbLine = new StringBuilder();
+ sbLine.append("[");
+ sbLine.append(currentTime);
+ sbLine.append("] Wake up ");
+ sbLine.append(appName);
+ winBollNewsBean.setMessage(sbLine.toString());
+
+ addWinBoLLNewsBean(context, winBollNewsBean);
+
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }
+ } catch (IOException e) {
+ LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
+ }
+ }
+ }
+ }
+
+ //
+ // 加入新报告信息
+ //
+ public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) {
+ initWinBoLLNewsBeanList(context);
+ _WinBoLLNewsBeanList.add(0, bean);
+ // 控制记录总数
+ while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) {
+ _WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1);
+ }
+ WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
+ }
+
+ synchronized static void initWinBoLLNewsBeanList(Context context) {
+ if (_WinBoLLNewsBeanList == null) {
+ _WinBoLLNewsBeanList = new ArrayList();
+ WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
+ }
+ if (_WinBoLLNewsBeanList == null) {
+ _WinBoLLNewsBeanList = new ArrayList();
+ WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
+ }
+ }
+
+ private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
+ LogUtils.d(TAG, "updateAppWidget(...)");
+
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news);
+ //设置按钮点击事件
+ Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class);
+ intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE);
+ PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT);
+ views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre);
+ Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class);
+ intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT);
+ PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);
+ views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext);
+
+ views.setTextViewText(R.id.tv_msg, getPageInfo());
+ views.setTextViewText(R.id.tv_news, getMessage());
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+
+ public static String getMessage() {
+ ArrayList msgTemp = new ArrayList();
+ if (_WinBoLLNewsBeanList != null) {
+ int start = _OnePageLinesCount * _CurrentPageIndex;
+ start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1;
+ for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) {
+ msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage());
+ }
+ String message = String.join("\n", msgTemp);
+ return message;
+ }
+ return "";
+ }
+
+ public static void prePage(Context context) {
+ if (_WinBoLLNewsBeanList != null) {
+ if (_CurrentPageIndex > 0) {
+ _CurrentPageIndex = _CurrentPageIndex - 1;
+ }
+ Intent intentWidget = new Intent(context, APPNewsWidget.class);
+ intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
+ context.sendBroadcast(intentWidget);
+ }
+ }
+
+ public static void nextPage(Context context) {
+ if (_WinBoLLNewsBeanList != null) {
+ if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) {
+ _CurrentPageIndex = _CurrentPageIndex + 1;
+ }
+ Intent intentWidget = new Intent(context, APPNewsWidget.class);
+ intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
+ context.sendBroadcast(intentWidget);
+ }
+ }
+
+ String getPageInfo() {
+ if (_WinBoLLNewsBeanList == null) {
+ return "0/0";
+ }
+ int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount;
+ int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1);
+ return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount);
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java
new file mode 100644
index 00000000..cf3d4ee1
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java
@@ -0,0 +1,63 @@
+package cc.winboll.studio.appbase.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.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.R;
+import cc.winboll.studio.libappbase.utils.ServiceUtils;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import cc.winboll.studio.libappbase.utils.ToastUtils;
+
+public class StatusWidget extends AppWidgetProvider {
+
+ public static final String TAG = "StatusWidget";
+
+ public static final String ACTION_STATUS_UPDATE = "cc.winboll.studio.libappbase.widgets.APPWidget.ACTION_STATUS_UPDATE";
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ if (intent.getAction().equals(ACTION_STATUS_UPDATE)) {
+ ToastUtils.show("Test");
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, StatusWidget.class));
+ for (int appWidgetId : appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId);
+ }
+ }
+ }
+
+ private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_status);
+ //设置按钮点击事件
+ Intent intentAppButton = new Intent(context, StatusWidgetClickListener.class);
+ intentAppButton.setAction(StatusWidgetClickListener.ACTION_IVAPP);
+ PendingIntent pendingIntentAppButton = PendingIntent.getBroadcast(context, 0, intentAppButton, PendingIntent.FLAG_UPDATE_CURRENT);
+ views.setOnClickPendingIntent(R.id.ivapp, pendingIntentAppButton);
+
+// boolean isActive = ServiceUtils.isServiceRunning(context, TestService.class.getName());
+// if (isActive) {
+// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher);
+// } else {
+// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher_disable);
+// }
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+}
diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java
new file mode 100644
index 00000000..911b1d64
--- /dev/null
+++ b/winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java
@@ -0,0 +1,33 @@
+package cc.winboll.studio.libappbase.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.utils.ToastUtils;
+
+public class StatusWidgetClickListener extends BroadcastReceiver {
+
+ public static final String TAG = "APPWidgetClickListener";
+
+ public static final String ACTION_IVAPP = "cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ LogUtils.d(TAG, String.format("action %s", action));
+ return;
+ }
+ if (action.equals(ACTION_IVAPP)) {
+ ToastUtils.show("ACTION_LAUNCHER");
+ } else {
+ LogUtils.d(TAG, String.format("action %s", action));
+ }
+ }
+}
diff --git a/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml b/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..c7bd21db
--- /dev/null
+++ b/winboll/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/bg_shadow.xml b/winboll/src/main/res/drawable/bg_shadow.xml
new file mode 100644
index 00000000..6d3d8989
--- /dev/null
+++ b/winboll/src/main/res/drawable/bg_shadow.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_dev_connected.xml b/winboll/src/main/res/drawable/ic_dev_connected.xml
new file mode 100644
index 00000000..1fb2f264
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_dev_connected.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/winboll/src/main/res/drawable/ic_dev_disconnected.xml b/winboll/src/main/res/drawable/ic_dev_disconnected.xml
new file mode 100644
index 00000000..42679750
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_dev_disconnected.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/winboll/src/main/res/drawable/ic_email.xml b/winboll/src/main/res/drawable/ic_email.xml
new file mode 100644
index 00000000..d526b262
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_email.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_email_alert.xml b/winboll/src/main/res/drawable/ic_email_alert.xml
new file mode 100644
index 00000000..f3ed6134
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_email_alert.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_iw.xml b/winboll/src/main/res/drawable/ic_iw.xml
new file mode 100644
index 00000000..5c5c1917
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_iw.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher.xml b/winboll/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 00000000..21f28b1a
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_background.xml b/winboll/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..d5fccc53
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_beta.xml b/winboll/src/main/res/drawable/ic_launcher_beta.xml
new file mode 100644
index 00000000..c304fdc1
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher_beta.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_disable.xml b/winboll/src/main/res/drawable/ic_launcher_disable.xml
new file mode 100644
index 00000000..174f475c
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher_disable.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_foreground.xml b/winboll/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..872b04e2
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml b/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml
new file mode 100644
index 00000000..4622116b
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_launcher_stage.jpg b/winboll/src/main/res/drawable/ic_launcher_stage.jpg
new file mode 100644
index 00000000..4d7d620b
Binary files /dev/null and b/winboll/src/main/res/drawable/ic_launcher_stage.jpg differ
diff --git a/winboll/src/main/res/drawable/ic_miapp.png b/winboll/src/main/res/drawable/ic_miapp.png
new file mode 100644
index 00000000..8d0939b8
Binary files /dev/null and b/winboll/src/main/res/drawable/ic_miapp.png differ
diff --git a/winboll/src/main/res/drawable/ic_winboll_help.xml b/winboll/src/main/res/drawable/ic_winboll_help.xml
new file mode 100644
index 00000000..564175fa
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_winboll_help.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_winboll_log.xml b/winboll/src/main/res/drawable/ic_winboll_log.xml
new file mode 100644
index 00000000..011f2b2c
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_winboll_log.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_winboll_logo.xml b/winboll/src/main/res/drawable/ic_winboll_logo.xml
new file mode 100644
index 00000000..ea28987a
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_winboll_logo.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/ic_winboll_point.xml b/winboll/src/main/res/drawable/ic_winboll_point.xml
new file mode 100644
index 00000000..48028cc4
--- /dev/null
+++ b/winboll/src/main/res/drawable/ic_winboll_point.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/winboll/src/main/res/drawable/shape_gradient.xml b/winboll/src/main/res/drawable/shape_gradient.xml
new file mode 100644
index 00000000..c164fe9d
--- /dev/null
+++ b/winboll/src/main/res/drawable/shape_gradient.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/winboll/src/main/res/drawable/view_border.xml b/winboll/src/main/res/drawable/view_border.xml
new file mode 100644
index 00000000..58b374aa
--- /dev/null
+++ b/winboll/src/main/res/drawable/view_border.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_logon.xml b/winboll/src/main/res/layout/activity_logon.xml
new file mode 100644
index 00000000..2a2c2d16
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_logon.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 00000000..2c54f8c8
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_main.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/layout/activity_main2.xml b/winboll/src/main/res/layout/activity_main2.xml
new file mode 100644
index 00000000..135b8451
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_main2.xml
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 00000000..b3dd223c
--- /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 00000000..697e6136
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_new2.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 00000000..ac73fab4
--- /dev/null
+++ b/winboll/src/main/res/layout/activity_yun.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 00000000..700d939a
--- /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 00000000..e85e3d2e
--- /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 00000000..b115a06c
--- /dev/null
+++ b/winboll/src/main/res/layout/widget_status.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/winboll/src/main/res/menu/toolbar_main2.xml b/winboll/src/main/res/menu/toolbar_main2.xml
new file mode 100644
index 00000000..41657dd1
--- /dev/null
+++ b/winboll/src/main/res/menu/toolbar_main2.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 00000000..eca70cfe
--- /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 00000000..eca70cfe
--- /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 00000000..a2f59082
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 00000000..1b523998
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 00000000..ff10afd6
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 00000000..115a4c76
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 00000000..dcd3cd80
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 00000000..459ca609
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 00000000..8ca12fe0
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 00000000..8e19b410
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 00000000..b824ebdd
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 00000000..4c19a13c
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/attrs.xml b/winboll/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..1221d426
--- /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 00000000..479769a0
--- /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 00000000..0a297170
--- /dev/null
+++ b/winboll/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ 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 00000000..df38db8f
--- /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 00000000..ef5fd332
--- /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/widget_provider_info_status.xml b/winboll/src/main/res/xml/widget_provider_info_status.xml
new file mode 100644
index 00000000..5e199843
--- /dev/null
+++ b/winboll/src/main/res/xml/widget_provider_info_status.xml
@@ -0,0 +1,8 @@
+
+
+