From 58369560b9a641312e74c0bae1aaa07bea9066e7 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 28 Sep 2025 13:55:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5APPBase=E8=BD=AC=E7=A7=BB?= =?UTF-8?q?=E6=9D=A5=E7=9A=84=E6=BA=90=E7=A0=81=EF=BC=88=E6=9C=AA=E8=B0=83?= =?UTF-8?q?=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- winboll/build.gradle | 6 +- winboll/src/main/AndroidManifest.xml | 193 ++++++++++- .../winboll/studio/winboll/MainActivity.java | 9 +- .../winboll/studio/winboll/MyTileService.java | 79 +++++ .../winboll/activities/LogonActivity.java | 150 +++++++++ .../winboll/activities/New2Activity.java | 77 +++++ .../winboll/activities/NewActivity.java | 76 +++++ .../winboll/activities/WinBoLLActivity.java | 61 ++++ .../activities/WinBoLLUnitTestActivity.java | 164 +++++++++ .../winboll/activities/YunActivity.java | 126 +++++++ .../winboll/handlers/MainServiceHandler.java | 38 +++ .../winboll/models/MainServiceBean.java | 67 ++++ .../models/TestDemoBindServiceBean.java | 67 ++++ .../winboll/models/TestDemoServiceBean.java | 68 ++++ .../winboll/models/WinBoLLNewsBean.java | 70 ++++ .../receivers/APPNewsWidgetClickListener.java | 36 ++ .../winboll/receivers/MainReceiver.java | 115 +++++++ .../receivers/MyBroadcastReceiver.java | 29 ++ .../winboll/services/AssistantService.java | 138 ++++++++ .../studio/winboll/services/MainService.java | 317 ++++++++++++++++++ .../winboll/services/TestDemoBindService.java | 179 ++++++++++ .../winboll/services/TestDemoService.java | 155 +++++++++ .../cc/winboll/studio/winboll/sos/SOS.java | 59 ++++ .../studio/winboll/sos/SOSCenterService.java | 182 ++++++++++ .../winboll/sos/SOSCenterServiceModel.java | 69 ++++ .../winboll/sos/SOSCenterServiceReceiver.java | 29 ++ .../winboll/studio/winboll/sos/SOSObject.java | 86 +++++ .../winboll/threads/MainServiceThread.java | 54 +++ .../studio/winboll/utils/TermuxUtils.java | 31 ++ .../studio/winboll/widgets/APPNewsWidget.java | 187 +++++++++++ .../studio/winboll/widgets/StatusWidget.java | 63 ++++ .../widgets/StatusWidgetClickListener.java | 33 ++ winboll/src/main/res/drawable/bg_shadow.xml | 41 +++ winboll/src/main/res/drawable/ic_cloud.xml | 11 + .../main/res/drawable/ic_cloud_outline.xml | 11 + .../main/res/drawable/ic_dev_connected.xml | 11 + .../main/res/drawable/ic_dev_disconnected.xml | 11 + winboll/src/main/res/drawable/ic_email.xml | 11 + .../src/main/res/drawable/ic_email_alert.xml | 11 + winboll/src/main/res/drawable/ic_launcher.xml | 13 + .../main/res/drawable/ic_launcher_disable.xml | 13 + .../res/drawable/ic_launcher_foreground.xml | 10 + .../ic_launcher_foreground_disable.xml | 10 + winboll/src/main/res/drawable/ic_miapp.png | Bin 0 -> 21452 bytes .../src/main/res/drawable/ic_winboll_help.xml | 27 ++ .../src/main/res/drawable/ic_winboll_log.xml | 41 +++ .../src/main/res/drawable/ic_winboll_logo.xml | 48 +++ .../main/res/drawable/ic_winboll_point.xml | 20 ++ .../src/main/res/drawable/shape_gradient.xml | 10 + winboll/src/main/res/drawable/view_border.xml | 8 + .../src/main/res/layout/activity_logon.xml | 68 ++++ winboll/src/main/res/layout/activity_main.xml | 6 + .../src/main/res/layout/activity_main2.xml | 215 ++++++++++++ winboll/src/main/res/layout/activity_new.xml | 43 +++ winboll/src/main/res/layout/activity_new2.xml | 43 +++ winboll/src/main/res/layout/activity_yun.xml | 63 ++++ winboll/src/main/res/layout/widget_news.xml | 51 +++ winboll/src/main/res/layout/widget_status.xml | 15 + winboll/src/main/res/menu/toolbar_main2.xml | 20 ++ .../main/res/xml/network_security_config.xml | 12 + .../main/res/xml/widget_provider_info_sos.xml | 7 + .../res/xml/widget_provider_info_status.xml | 8 + 62 files changed, 3860 insertions(+), 11 deletions(-) create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/MyTileService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/LogonActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/New2Activity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/NewActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/WinBoLLUnitTestActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/activities/YunActivity.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/handlers/MainServiceHandler.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/models/MainServiceBean.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoBindServiceBean.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/models/TestDemoServiceBean.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/models/WinBoLLNewsBean.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/receivers/APPNewsWidgetClickListener.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/receivers/MainReceiver.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/receivers/MyBroadcastReceiver.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/services/AssistantService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/services/MainService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoBindService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/services/TestDemoService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterService.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceModel.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSCenterServiceReceiver.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/sos/SOSObject.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/threads/MainServiceThread.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/utils/TermuxUtils.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/widgets/APPNewsWidget.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidget.java create mode 100644 winboll/src/main/java/cc/winboll/studio/winboll/widgets/StatusWidgetClickListener.java create mode 100644 winboll/src/main/res/drawable/bg_shadow.xml create mode 100644 winboll/src/main/res/drawable/ic_cloud.xml create mode 100644 winboll/src/main/res/drawable/ic_cloud_outline.xml create mode 100644 winboll/src/main/res/drawable/ic_dev_connected.xml create mode 100644 winboll/src/main/res/drawable/ic_dev_disconnected.xml create mode 100644 winboll/src/main/res/drawable/ic_email.xml create mode 100644 winboll/src/main/res/drawable/ic_email_alert.xml create mode 100644 winboll/src/main/res/drawable/ic_launcher.xml create mode 100644 winboll/src/main/res/drawable/ic_launcher_disable.xml create mode 100644 winboll/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 winboll/src/main/res/drawable/ic_launcher_foreground_disable.xml create mode 100644 winboll/src/main/res/drawable/ic_miapp.png create mode 100644 winboll/src/main/res/drawable/ic_winboll_help.xml create mode 100644 winboll/src/main/res/drawable/ic_winboll_log.xml create mode 100644 winboll/src/main/res/drawable/ic_winboll_logo.xml create mode 100644 winboll/src/main/res/drawable/ic_winboll_point.xml create mode 100644 winboll/src/main/res/drawable/shape_gradient.xml create mode 100644 winboll/src/main/res/drawable/view_border.xml create mode 100644 winboll/src/main/res/layout/activity_logon.xml create mode 100644 winboll/src/main/res/layout/activity_main2.xml create mode 100644 winboll/src/main/res/layout/activity_new.xml create mode 100644 winboll/src/main/res/layout/activity_new2.xml create mode 100644 winboll/src/main/res/layout/activity_yun.xml create mode 100644 winboll/src/main/res/layout/widget_news.xml create mode 100644 winboll/src/main/res/layout/widget_status.xml create mode 100644 winboll/src/main/res/menu/toolbar_main2.xml create mode 100644 winboll/src/main/res/xml/network_security_config.xml create mode 100644 winboll/src/main/res/xml/widget_provider_info_sos.xml create mode 100644 winboll/src/main/res/xml/widget_provider_info_status.xml diff --git a/winboll/build.gradle b/winboll/build.gradle index 7cc77dfe..923a34c5 100644 --- a/winboll/build.gradle +++ b/winboll/build.gradle @@ -66,8 +66,8 @@ dependencies { //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.1' - api 'cc.winboll.studio:libappbase:15.8.0' + api 'cc.winboll.studio:libapputils:15.8.2' + api 'cc.winboll.studio:libappbase:15.8.2' } diff --git a/winboll/src/main/AndroidManifest.xml b/winboll/src/main/AndroidManifest.xml index 08260eef..a098c3ce 100644 --- a/winboll/src/main/AndroidManifest.xml +++ b/winboll/src/main/AndroidManifest.xml @@ -3,6 +3,15 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="cc.winboll.studio.winboll"> + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java index 6c9d1423..ce829c86 100644 --- a/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java +++ b/winboll/src/main/java/cc/winboll/studio/winboll/MainActivity.java @@ -1,18 +1,16 @@ package cc.winboll.studio.winboll; -import android.graphics.drawable.Drawable; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogView; -import com.hjq.toast.ToastUtils; public class MainActivity extends AppCompatActivity { public static final String TAG = "MainActivity"; LogView mLogView; + private TerminalView mTerminalView; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,7 +37,10 @@ public class MainActivity extends AppCompatActivity { mLogView = findViewById(R.id.logview); - ToastUtils.show("onCreate"); + mTerminalView = findViewById(R.id.terminalView); + + // 示例:模拟用户输入 "ls" 命令 + mTerminalView.sendInput("ls"); } @Override 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/bg_shadow.xml b/winboll/src/main/res/drawable/bg_shadow.xml new file mode 100644 index 00000000..6d3d8989 --- /dev/null +++ b/winboll/src/main/res/drawable/bg_shadow.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/winboll/src/main/res/drawable/ic_cloud.xml b/winboll/src/main/res/drawable/ic_cloud.xml new file mode 100644 index 00000000..62b99afd --- /dev/null +++ b/winboll/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/winboll/src/main/res/drawable/ic_cloud_outline.xml b/winboll/src/main/res/drawable/ic_cloud_outline.xml new file mode 100644 index 00000000..fb06b79a --- /dev/null +++ b/winboll/src/main/res/drawable/ic_cloud_outline.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file 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_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_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_miapp.png b/winboll/src/main/res/drawable/ic_miapp.png new file mode 100644 index 0000000000000000000000000000000000000000..8d0939b8c41123d5c673630370272c7dd74dadd3 GIT binary patch literal 21452 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 + + + + 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 @@ + + + + + + + + + + + + + +