diff --git a/powerbell/build.gradle b/powerbell/build.gradle
index 55533e4..df9c894 100644
--- a/powerbell/build.gradle
+++ b/powerbell/build.gradle
@@ -18,22 +18,19 @@ def genVersionName(def versionName){
}
android {
-
- // 关键:改为你已安装的 SDK 32(≥ targetSdkVersion 30,兼容已安装环境)
- compileSdkVersion 32
-
- // 直接使用已安装的构建工具 33.0.3(无需修改)
- buildToolsVersion "33.0.3"
+ // 适配MIUI12
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.powerbell"
- minSdkVersion 23
+ minSdkVersion 21
targetSdkVersion 30
versionCode 7
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
- versionName "15.14"
+ versionName "15.15"
if(true) {
versionName = genVersionName("${versionName}")
}
@@ -83,12 +80,12 @@ dependencies {
//api 'androidx.fragment:fragment:1.1.0'
// WinBoLL库 nexus.winboll.cc 地址
- api 'cc.winboll.studio:libaes:15.12.13'
- api 'cc.winboll.studio:libappbase:15.14.2'
+ api 'cc.winboll.studio:libaes:15.15.2'
+ api 'cc.winboll.studio:libappbase:15.15.11'
// WinBoLL备用库 jitpack.io 地址
- //api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
- //api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
+ //api 'com.github.ZhanGSKen:AES:aes-v15.15.2'
+ //api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
//api fileTree(dir: 'libs', include: ['*.aar'])
api fileTree(dir: 'libs', include: ['*.jar'])
diff --git a/powerbell/build.properties b/powerbell/build.properties
index 0397356..bf6afbd 100644
--- a/powerbell/build.properties
+++ b/powerbell/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Wed Jan 07 18:09:34 HKT 2026
-stageCount=50
+#Wed Mar 11 04:24:14 HKT 2026
+stageCount=17
libraryProject=
-baseVersion=15.14
-publishVersion=15.14.49
+baseVersion=15.15
+publishVersion=15.15.16
buildCount=0
-baseBetaVersion=15.14.50
+baseBetaVersion=15.15.17
diff --git a/powerbell/src/main/AndroidManifest.xml b/powerbell/src/main/AndroidManifest.xml
index 4d60df8..62581a7 100644
--- a/powerbell/src/main/AndroidManifest.xml
+++ b/powerbell/src/main/AndroidManifest.xml
@@ -301,7 +301,8 @@
android:process=":main"
android:stopWithTask="false"/>
-
+
+
-
+
\ No newline at end of file
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java
index 0ad0bc2..b482573 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java
@@ -13,13 +13,13 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewStub;
import androidx.appcompat.widget.Toolbar;
-import cc.winboll.studio.libaes.activitys.AboutActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.powerbell.activities.AboutActivity;
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
import cc.winboll.studio.powerbell.activities.BatteryReportActivity;
import cc.winboll.studio.powerbell.activities.ClearRecordActivity;
@@ -550,7 +550,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
LogUtils.d(TAG, "startAboutActivity() 调用");
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAppInfo();
- aboutIntent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), aboutIntent, AboutActivity.class);
LogUtils.d(TAG, "startAboutActivity: 关于页面已启动");
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/AboutActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/AboutActivity.java
new file mode 100644
index 0000000..21e1fa6
--- /dev/null
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/AboutActivity.java
@@ -0,0 +1,81 @@
+package cc.winboll.studio.powerbell.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import androidx.appcompat.widget.Toolbar;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.models.APPInfo;
+import cc.winboll.studio.libappbase.views.AboutView;
+import cc.winboll.studio.powerbell.R;
+
+/**
+ * @Author 豆包&ZhanGSKen
+ * @Date 2026/01/13 11:25
+ * @Describe 应用介绍窗口
+ */
+public class AboutActivity extends WinBoLLActivity {
+
+ public static final String TAG = "AboutActivity";
+ private Toolbar mToolbar;
+ @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_about);
+
+ // 设置工具栏
+ initToolbar();
+
+ AboutView aboutView = findViewById(R.id.aboutview);
+ aboutView.setAPPInfo(genDefaultAppInfo());
+ }
+
+ private void initToolbar() {
+ LogUtils.d(TAG, "initToolbar() 开始初始化");
+ mToolbar = findViewById(R.id.toolbar);
+ if (mToolbar == null) {
+ LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
+ return;
+ }
+ setSupportActionBar(mToolbar);
+ mToolbar.setSubtitle(getTag());
+ mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtils.d(TAG, "导航栏 点击返回按钮");
+ finish();
+ }
+ });
+ LogUtils.d(TAG, "initToolbar() 配置完成");
+ }
+
+ private APPInfo genDefaultAppInfo() {
+ LogUtils.d(TAG, "genDefaultAppInfo() 调用");
+ String branchName = "powerbell";
+ APPInfo appInfo = new APPInfo();
+ appInfo.setAppName(getString(R.string.app_name));
+ appInfo.setAppIcon(R.drawable.ic_winboll);
+ appInfo.setAppDescription(getString(R.string.app_description));
+ appInfo.setAppGitName("PowerBell");
+ appInfo.setAppGitOwner("Studio");
+ appInfo.setAppGitAPPBranch(branchName);
+ appInfo.setAppGitAPPSubProjectFolder(branchName);
+ appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=PowerBell");
+ appInfo.setAppAPKName("PowerBell");
+ appInfo.setAppAPKFolderName("PowerBell");
+ LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
+ return appInfo;
+ }
+}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/SettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/SettingsActivity.java
index e6c02e1..fca94e6 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/SettingsActivity.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/SettingsActivity.java
@@ -23,20 +23,25 @@ import java.lang.reflect.Field;
/**
* 应用设置窗口,提供应用配置项的统一入口
* 适配 API30,基于 Java7 开发
- * @Author ZhanGSKen&豆包大模型
- * @Date 2025/11/27 14:26
- * @Describe 应用设置窗口
+ * @Author 豆包&ZhanGSKen
+ * @Date 2025年11月27日14时26分00秒
+ * @LastEditTime 2026-02-28
+ * @Describe 应用设置窗口(主开关联动子开关启用/禁用,主开关关闭则子开关禁用并取消勾选)
*/
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
- // ======================== 静态常量 =========================
+ // ======================== 静态常量区 =========================
public static final String TAG = "SettingsActivity";
- // 权限请求常量(为后续读取媒体图片权限预留)
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
- // ======================== 成员变量 =========================
- private Toolbar mToolbar; // 顶部工具栏
+ // ======================== 成员属性区 =========================
+ private Toolbar mToolbar;
+ private CheckBox cbUsePowerTts; // 用电TTS(主开关)
+ private CheckBox cbChargeTts; // 充电TTS(主开关)
+ private CheckBox cbUseageTtsBattary; // 用电TTS带电量提醒(子开关)
+ private CheckBox cbChargeTtsBattary; // 充电TTS带电量提醒(子开关)
+ private CheckBox cbTtsWhenNotifyBattery; // 👉 新增:通知电量时同时播放TTS
- // ======================== 接口实现方法 =========================
+ // ======================== 接口实现区 =========================
@Override
public Activity getActivity() {
return this;
@@ -47,96 +52,195 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
return TAG;
}
- // ======================== 生命周期方法 =========================
+ // ======================== 生命周期区 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
- LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化开始");
+ LogUtils.d(TAG, "onCreate: 应用设置页面初始化开始");
- // 初始化工具栏
initToolbar();
-
- ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
- if (thoughtfulServiceBean == null) {
- thoughtfulServiceBean = new ThoughtfulServiceBean();
- }
- ((CheckBox)findViewById(R.id.activitysettingsCheckBox1)).setChecked(thoughtfulServiceBean.isEnableUsePowerTts());
- ((CheckBox)findViewById(R.id.activitysettingsCheckBox2)).setChecked(thoughtfulServiceBean.isEnableChargeTts());
+ initTtsCheckBoxes();
+ initTtsCheckBoxStatus();
- LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
+ LogUtils.d(TAG, "onCreate: 应用设置页面初始化完成");
}
- // ======================== UI初始化方法 =========================
+ // ======================== UI初始化区 =========================
/**
- * 初始化顶部工具栏,设置导航返回与样式
+ * 初始化顶部工具栏
*/
private void initToolbar() {
+ LogUtils.d(TAG, "initToolbar: 工具栏初始化开始");
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
- // 设置工具栏副标题与标题样式
mToolbar.setSubtitle(getTag());
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
- // 显示返回按钮
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- // 绑定导航点击事件
+
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- LogUtils.d(TAG, "【导航栏】点击返回");
+ LogUtils.d(TAG, "initToolbar-navigationOnClick: 点击导航返回按钮");
finish();
}
});
- LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
+ LogUtils.d(TAG, "initToolbar: 工具栏初始化完成");
}
- public void onCheckTTSDrawOverlaysPermission(View view) {
- canDrawOverlays();
- }
+ /**
+ * 绑定TTS相关复选框控件
+ */
+ private void initTtsCheckBoxes() {
+ LogUtils.d(TAG, "initTtsCheckBoxes: TTS复选框绑定开始");
+ cbUsePowerTts = findViewById(R.id.activitysettingsCheckBox1);
+ cbChargeTts = findViewById(R.id.activitysettingsCheckBox2);
+ cbUseageTtsBattary = findViewById(R.id.activitysettingsCheckBox3);
+ cbChargeTtsBattary = findViewById(R.id.activitysettingsCheckBox4);
+ cbTtsWhenNotifyBattery = findViewById(R.id.activitysettingsCheckBox5); // 👉 新增绑定
+ LogUtils.d(TAG, "initTtsCheckBoxes: TTS复选框绑定完成");
+ }
- public void onEnableChargeTts(View view) {
- ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
- if (thoughtfulServiceBean == null) {
- thoughtfulServiceBean = new ThoughtfulServiceBean();
- }
- thoughtfulServiceBean.setIsEnableChargeTts(((CheckBox)view).isChecked());
- ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
- }
+ /**
+ * 初始化TTS复选框初始状态
+ */
+ private void initTtsCheckBoxStatus() {
+ LogUtils.d(TAG, "initTtsCheckBoxStatus: TTS复选框状态初始化开始");
+ ThoughtfulServiceBean bean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
+ if (bean == null) {
+ LogUtils.d(TAG, "initTtsCheckBoxStatus: 未读取到配置Bean,创建新实例");
+ bean = new ThoughtfulServiceBean();
+ }
- public void onEnableUsePowerTts(View view) {
- ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
- if (thoughtfulServiceBean == null) {
- thoughtfulServiceBean = new ThoughtfulServiceBean();
- }
- thoughtfulServiceBean.setIsEnableUsePowerTts(((CheckBox)view).isChecked());
- ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
- }
+ boolean useMainOpen = bean.isEnableUsePowerTts();
+ boolean chargeMainOpen = bean.isEnableChargeTts();
+ cbUsePowerTts.setChecked(useMainOpen);
+ cbChargeTts.setChecked(chargeMainOpen);
+ cbUseageTtsBattary.setChecked(bean.isEnableUseageTtsWithBattary());
+ cbChargeTtsBattary.setChecked(bean.isEnableChargeTtsWithBattary());
+ cbTtsWhenNotifyBattery.setChecked(bean.isEnableTtsWhenNotifyBattery()); // 👉 新增赋值
+ cbUseageTtsBattary.setEnabled(useMainOpen);
+ cbChargeTtsBattary.setEnabled(chargeMainOpen);
- /**
+ LogUtils.d(TAG, "initTtsCheckBoxStatus: 主开关状态-用电TTS:" + useMainOpen + " 充电TTS:" + chargeMainOpen);
+ LogUtils.d(TAG, "initTtsCheckBoxStatus: TTS复选框状态初始化完成");
+ }
+
+ // ======================== 事件响应区 =========================
+ /**
+ * 悬浮窗权限检查入口
+ */
+ public void onCheckTTSDrawOverlaysPermission(View view) {
+ LogUtils.d(TAG, "onCheckTTSDrawOverlaysPermission: 触发悬浮窗权限检查");
+ canDrawOverlays();
+ }
+
+ /**
+ * 用电TTS主开关点击事件
+ */
+ public void onEnableUsePowerTts(View view) {
+ boolean isChecked = cbUsePowerTts.isChecked();
+ LogUtils.d(TAG, "onEnableUsePowerTts: 用电TTS主开关点击,切换后状态=" + isChecked);
+ // 主开关联动子开关
+ cbUseageTtsBattary.setEnabled(isChecked);
+ // 保存配置
+ ThoughtfulServiceBean bean = getThoughtfulServiceBean();
+ bean.setIsEnableUsePowerTts(isChecked);
+ ThoughtfulServiceBean.saveBean(this, bean);
+ LogUtils.d(TAG, "onEnableUsePowerTts: 用电TTS状态保存完成");
+ }
+
+ /**
+ * 充电TTS主开关点击事件
+ */
+ public void onEnableChargeTts(View view) {
+ boolean isChecked = cbChargeTts.isChecked();
+ LogUtils.d(TAG, "onEnableChargeTts: 充电TTS主开关点击,切换后状态=" + isChecked);
+ // 主开关联动子开关
+ cbChargeTtsBattary.setEnabled(isChecked);
+ // 保存配置
+ ThoughtfulServiceBean bean = getThoughtfulServiceBean();
+ bean.setIsEnableChargeTts(isChecked);
+ ThoughtfulServiceBean.saveBean(this, bean);
+ LogUtils.d(TAG, "onEnableChargeTts: 充电TTS状态保存完成");
+ }
+
+ /**
+ * 用电TTS带电量提醒子开关点击事件
+ */
+ public void onEnableUseageTtsWithBattary(View view) {
+ boolean isChecked = cbUseageTtsBattary.isChecked();
+ LogUtils.d(TAG, "onEnableUseageTtsWithBattary: 用电TTS电量提醒开关点击,切换后状态=" + isChecked);
+ ThoughtfulServiceBean bean = getThoughtfulServiceBean();
+ bean.setIsEnableUseageTtsWithBattary(isChecked);
+ ThoughtfulServiceBean.saveBean(this, bean);
+ LogUtils.d(TAG, "onEnableUseageTtsWithBattary: 用电TTS电量提醒状态保存完成");
+ }
+
+ /**
+ * 充电TTS带电量提醒子开关点击事件
+ */
+ public void onEnableChargeTtsWithBattary(View view) {
+ boolean isChecked = cbChargeTtsBattary.isChecked();
+ LogUtils.d(TAG, "onEnableChargeTtsWithBattary: 充电TTS电量提醒开关点击,切换后状态=" + isChecked);
+ ThoughtfulServiceBean bean = getThoughtfulServiceBean();
+ bean.setIsEnableChargeTtsWithBattary(isChecked);
+ ThoughtfulServiceBean.saveBean(this, bean);
+ LogUtils.d(TAG, "onEnableChargeTtsWithBattary: 充电TTS电量提醒状态保存完成");
+ }
+
+ /**
+ * 👉 新增:允许通知电量消息时同时播放TTS语音
+ */
+ public void onEnableTtsWhenNotifyBattery(View view) {
+ boolean isChecked = cbTtsWhenNotifyBattery.isChecked();
+ LogUtils.d(TAG, "onEnableTtsWhenNotifyBattery: 通知电量时播TTS开关状态=" + isChecked);
+ ThoughtfulServiceBean bean = getThoughtfulServiceBean();
+ bean.setIsEnableTtsWhenNotifyBattery(isChecked);
+ ThoughtfulServiceBean.saveBean(this, bean);
+ LogUtils.d(TAG, "onEnableTtsWhenNotifyBattery: 保存完成");
+ }
+
+ // ======================== 工具方法区 =========================
+ /**
+ * 获取配置Bean实例,避免重复代码
+ */
+ private ThoughtfulServiceBean getThoughtfulServiceBean() {
+ LogUtils.d(TAG, "getThoughtfulServiceBean: 获取配置Bean");
+ ThoughtfulServiceBean bean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
+ if (bean == null) {
+ LogUtils.d(TAG, "getThoughtfulServiceBean: 配置Bean为空,创建新实例");
+ bean = new ThoughtfulServiceBean();
+ }
+ return bean;
+ }
+
+ /**
* 悬浮窗权限检查与请求
*/
- void canDrawOverlays() {
- LogUtils.d(TAG, "onCanDrawOverlays: 检查悬浮窗权限");
- // API6.0+校验权限
+ void canDrawOverlays() {
+ LogUtils.d(TAG, "canDrawOverlays: 悬浮窗权限检查开始");
if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
- LogUtils.d(TAG, "onCanDrawOverlays: 未开启悬浮窗权限,发起请求");
+ LogUtils.d(TAG, "canDrawOverlays: 未开启悬浮窗权限,发起请求");
showDrawOverlayRequestDialog();
} else {
+ LogUtils.d(TAG, "canDrawOverlays: 悬浮窗权限已开启");
ToastUtils.show("悬浮窗权限已开启");
}
}
-
/**
* 显示悬浮窗权限请求对话框
*/
private void showDrawOverlayRequestDialog() {
+ LogUtils.d(TAG, "showDrawOverlayRequestDialog: 显示悬浮窗权限请求弹窗");
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("权限请求")
.setMessage("为保证通话监听功能正常,需开启悬浮窗权限")
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
+ LogUtils.d(TAG, "showDrawOverlayRequestDialog-去设置: 点击跳转权限页面");
dialog.dismiss();
jumpToDrawOverlaySettings();
}
@@ -144,12 +248,12 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
.setNegativeButton("稍后", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
+ LogUtils.d(TAG, "showDrawOverlayRequestDialog-稍后: 点击取消请求");
dialog.dismiss();
}
})
.create();
- // 解决对话框焦点问题
if (dialog.getWindow() != null) {
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
@@ -162,23 +266,21 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
* 跳转悬浮窗权限设置页面(反射适配低版本)
*/
private void jumpToDrawOverlaySettings() {
- LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置");
+ LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置页面");
try {
- // 反射获取设置页面Action(避免高版本API依赖)
Class> settingsClazz = Settings.class;
Field actionField = settingsClazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
String action = (String) actionField.get(null);
- // 跳转当前应用权限设置页
Intent intent = new Intent(action);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
+ LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转权限页面意图已发送");
} catch (Exception e) {
LogUtils.e(TAG, "jumpToDrawOverlaySettings: 跳转权限设置失败", e);
Toast.makeText(this, "请手动在设置中开启悬浮窗权限", Toast.LENGTH_LONG).show();
}
}
-
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java
index 3c33ce1..eb349d4 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java
@@ -4,7 +4,9 @@ import android.os.Handler;
import android.os.Message;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.models.NotificationMessage;
+import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
import cc.winboll.studio.powerbell.services.ControlCenterService;
+import cc.winboll.studio.powerbell.threads.TTSRemindThread;
import java.lang.ref.WeakReference;
/**
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ThoughtfulServiceBean.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ThoughtfulServiceBean.java
index 7332325..0531a34 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ThoughtfulServiceBean.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ThoughtfulServiceBean.java
@@ -24,12 +24,19 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
// JSON序列化字段常量 杜绝硬编码
public static final String JSON_FIELD_IS_ENABLE_CHARGE_TTS = "isEnableChargeTts";
public static final String JSON_FIELD_IS_ENABLE_USE_POWER_TTS = "isEnableUsePowerTts";
+ public static final String JSON_FIELD_IS_ENABLE_USAGE_TTS_WITH_BATTERY = "isEnableUseageTtsWithBattary";
+ public static final String JSON_FIELD_IS_ENABLE_CHARGE_TTS_WITH_BATTERY = "isEnableChargeTtsWithBattary";
+ // 👉 新增:通知电量消息时同时播放TTS
+ public static final String JSON_FIELD_IS_ENABLE_TTS_WHEN_NOTIFY_BATTERY = "isEnableTtsWhenNotifyBattery";
// ====================== 核心成员变量 - 私有封装 ======================
- private boolean isEnableChargeTts = false; // 是否启用 充电TTS贴心语音服务
- private boolean isEnableUsePowerTts = false; // 是否启用 用电TTS贴心语音服务
+ private boolean isEnableChargeTts = false; // 是否启用 充电TTS贴心语音服务
+ private boolean isEnableUsePowerTts = false; // 是否启用 用电TTS贴心语音服务
+ private boolean isEnableUseageTtsWithBattary = false; // 用电TTS加入电量提醒
+ private boolean isEnableChargeTtsWithBattary = false; // 充电TTS加入电量提醒
+ private boolean isEnableTtsWhenNotifyBattery = false; // 👉 允许通知电量消息时同时播放TTS语音
- // ====================== Parcelable 静态创建器 (API30标准写法 必须public static final) ======================
+ // ====================== Parcelable 静态创建器 ======================
public static final Creator CREATOR = new Creator() {
@Override
public ThoughtfulServiceBean createFromParcel(Parcel source) {
@@ -43,35 +50,34 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
}
};
- // ====================== 构造方法区 (无参+有参+Parcel构造 全覆盖) ======================
- /**
- * 无参构造 - JSON解析/反射实例化 必备
- */
+ // ====================== 构造方法区 ======================
public ThoughtfulServiceBean() {
LogUtils.d(TAG, "ThoughtfulServiceBean: 无参构造,初始化默认禁用所有TTS服务");
}
- /**
- * 全参构造 - 手动配置所有服务状态
- * @param isEnableChargeTts 充电TTS服务开关
- * @param isEnableUsePowerTts 用电TTS服务开关
- */
- public ThoughtfulServiceBean(boolean isEnableChargeTts, boolean isEnableUsePowerTts) {
+ public ThoughtfulServiceBean(boolean isEnableChargeTts, boolean isEnableUsePowerTts,
+ boolean isEnableUseageTtsWithBattary, boolean isEnableChargeTtsWithBattary,
+ boolean isEnableTtsWhenNotifyBattery) {
this.isEnableChargeTts = isEnableChargeTts;
this.isEnableUsePowerTts = isEnableUsePowerTts;
- LogUtils.d(TAG, "ThoughtfulServiceBean: 全参构造 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
+ this.isEnableUseageTtsWithBattary = isEnableUseageTtsWithBattary;
+ this.isEnableChargeTtsWithBattary = isEnableChargeTtsWithBattary;
+ this.isEnableTtsWhenNotifyBattery = isEnableTtsWhenNotifyBattery;
+ LogUtils.d(TAG, "ThoughtfulServiceBean: 全参构造 | 充电TTS=" + isEnableChargeTts + " | 用电TTS=" + isEnableUsePowerTts
+ + " | 用电TTS加电量=" + isEnableUseageTtsWithBattary + " | 充电TTS加电量=" + isEnableChargeTtsWithBattary
+ + " | 通知电量时播TTS=" + isEnableTtsWhenNotifyBattery);
}
- /**
- * Parcel反序列化构造 - Parcelable必备 私有私有化
- */
private ThoughtfulServiceBean(Parcel in) {
this.isEnableChargeTts = in.readByte() != 0;
this.isEnableUsePowerTts = in.readByte() != 0;
- LogUtils.d(TAG, "ThoughtfulServiceBean: Parcel构造解析完成 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
+ this.isEnableUseageTtsWithBattary = in.readByte() != 0;
+ this.isEnableChargeTtsWithBattary = in.readByte() != 0;
+ this.isEnableTtsWhenNotifyBattery = in.readByte() != 0; // 👉 新增反序列化
+ LogUtils.d(TAG, "ThoughtfulServiceBean: Parcel构造解析完成");
}
- // ====================== Getter/Setter 方法区 (封装成员变量 统一访问) ======================
+ // ====================== Getter/Setter 方法区 ======================
public boolean isEnableChargeTts() {
return isEnableChargeTts;
}
@@ -90,7 +96,35 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
this.isEnableUsePowerTts = isEnableUsePowerTts;
}
- // ====================== 重写父类 BaseBean 核心方法 (JSON序列化/反序列化 业务核心) ======================
+ public boolean isEnableUseageTtsWithBattary() {
+ return isEnableUseageTtsWithBattary;
+ }
+
+ public void setIsEnableUseageTtsWithBattary(boolean isEnableUseageTtsWithBattary) {
+ LogUtils.d(TAG, "setIsEnableUseageTtsWithBattary: 旧值=" + this.isEnableUseageTtsWithBattary + " | 新值=" + isEnableUseageTtsWithBattary);
+ this.isEnableUseageTtsWithBattary = isEnableUseageTtsWithBattary;
+ }
+
+ public boolean isEnableChargeTtsWithBattary() {
+ return isEnableChargeTtsWithBattary;
+ }
+
+ public void setIsEnableChargeTtsWithBattary(boolean isEnableChargeTtsWithBattary) {
+ LogUtils.d(TAG, "setIsEnableChargeTtsWithBattary: 旧值=" + this.isEnableChargeTtsWithBattary + " | 新值=" + isEnableChargeTtsWithBattary);
+ this.isEnableChargeTtsWithBattary = isEnableChargeTtsWithBattary;
+ }
+
+ // 👉 新增:通知电量时播放TTS
+ public boolean isEnableTtsWhenNotifyBattery() {
+ return isEnableTtsWhenNotifyBattery;
+ }
+
+ public void setIsEnableTtsWhenNotifyBattery(boolean isEnableTtsWhenNotifyBattery) {
+ LogUtils.d(TAG, "setIsEnableTtsWhenNotifyBattery: 旧值=" + this.isEnableTtsWhenNotifyBattery + " | 新值=" + isEnableTtsWhenNotifyBattery);
+ this.isEnableTtsWhenNotifyBattery = isEnableTtsWhenNotifyBattery;
+ }
+
+ // ====================== 重写父类 BaseBean 核心方法 ======================
@Override
public String getName() {
String className = ThoughtfulServiceBean.class.getName();
@@ -98,20 +132,17 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
return className;
}
- /**
- * JSON序列化 - 写入所有字段 适配持久化/网络传输
- */
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name(JSON_FIELD_IS_ENABLE_CHARGE_TTS).value(this.isEnableChargeTts);
jsonWriter.name(JSON_FIELD_IS_ENABLE_USE_POWER_TTS).value(this.isEnableUsePowerTts);
+ jsonWriter.name(JSON_FIELD_IS_ENABLE_USAGE_TTS_WITH_BATTERY).value(this.isEnableUseageTtsWithBattary);
+ jsonWriter.name(JSON_FIELD_IS_ENABLE_CHARGE_TTS_WITH_BATTERY).value(this.isEnableChargeTtsWithBattary);
+ jsonWriter.name(JSON_FIELD_IS_ENABLE_TTS_WHEN_NOTIFY_BATTERY).value(this.isEnableTtsWhenNotifyBattery); // 👉 新增JSON写入
LogUtils.d(TAG, "writeThisToJsonWriter: JSON序列化完成,所有TTS服务状态已写入");
}
- /**
- * JSON反序列化 - 读取字段生成实体 适配数据恢复
- */
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
ThoughtfulServiceBean bean = new ThoughtfulServiceBean();
@@ -125,6 +156,15 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
case JSON_FIELD_IS_ENABLE_USE_POWER_TTS:
bean.setIsEnableUsePowerTts(jsonReader.nextBoolean());
break;
+ case JSON_FIELD_IS_ENABLE_USAGE_TTS_WITH_BATTERY:
+ bean.setIsEnableUseageTtsWithBattary(jsonReader.nextBoolean());
+ break;
+ case JSON_FIELD_IS_ENABLE_CHARGE_TTS_WITH_BATTERY:
+ bean.setIsEnableChargeTtsWithBattary(jsonReader.nextBoolean());
+ break;
+ case JSON_FIELD_IS_ENABLE_TTS_WHEN_NOTIFY_BATTERY:
+ bean.setIsEnableTtsWhenNotifyBattery(jsonReader.nextBoolean()); // 👉 新增JSON读取
+ break;
default:
jsonReader.skipValue();
LogUtils.w(TAG, "readBeanFromJsonReader: 跳过未知JSON字段 = " + fieldName);
@@ -136,19 +176,19 @@ public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Seria
return bean;
}
- // ====================== 实现 Parcelable 接口方法 (组件间Intent传递必备 API30/Java7完美适配) ======================
+ // ====================== Parcelable 接口方法 ======================
@Override
public int describeContents() {
- return 0; // 无文件描述符等特殊内容,固定返回0即可
+ return 0;
}
- /**
- * Parcel序列化 - boolean用byte存储(Java7/API30标准写法 避免兼容性问题)
- */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (isEnableChargeTts ? 1 : 0));
dest.writeByte((byte) (isEnableUsePowerTts ? 1 : 0));
+ dest.writeByte((byte) (isEnableUseageTtsWithBattary ? 1 : 0));
+ dest.writeByte((byte) (isEnableChargeTtsWithBattary ? 1 : 0));
+ dest.writeByte((byte) (isEnableTtsWhenNotifyBattery ? 1 : 0)); // 👉 新增Parcel写入
LogUtils.d(TAG, "writeToParcel: Parcel序列化完成,所有TTS服务状态已写入");
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java
index 468d317..4fa10d8 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java
@@ -24,56 +24,55 @@ import cc.winboll.studio.powerbell.services.ThoughtfulService;
* @Describe 统一处理系统与应用内广播,同步电池状态与配置,保障多线程数据一致性
*/
public class ControlCenterServiceReceiver extends BroadcastReceiver {
- // ====================== 静态常量区(置顶归类,消除魔法值) ======================
+ // ====================== 静态常量区 ======================
public static final String TAG = "ControlCenterServiceReceiver";
- // 广播Action常量(带包名前缀防冲突)
public static final String ACTION_UPDATE_FOREGROUND_NOTIFICATION = "cc.winboll.studio.powerbell.action.ACTION_UPDATE_FOREGROUND_NOTIFICATION";
public static final String ACTION_APPCONFIG_CHANGED = "cc.winboll.studio.powerbell.action.ACTION_APPCONFIG_CHANGED";
public static final String EXTRA_APP_CONFIG_BEAN = "extra_app_config_bean";
- // 广播优先级与电量范围常量
private static final int BROADCAST_PRIORITY = IntentFilter.SYSTEM_HIGH_PRIORITY - 10;
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
- private static final int INVALID_BATTERY = -1; // 无效电量标识
+ private static final int INVALID_BATTERY = -1;
- // ====================== 静态状态标记(volatile保证多线程可见性) ======================
- private static volatile int sLastBatteryLevel = INVALID_BATTERY; // 上次电量(多线程可见)
- private static volatile boolean sIsCharging = false; // 上次充电状态(多线程可见)
+ // ====================== 静态状态(防抖动 + 防重复播报) ======================
+ private static volatile int sLastBatteryLevel = INVALID_BATTERY;
+ private static volatile boolean sIsCharging = false;
- // ====================== 成员变量区(弱引用防泄漏,按功能分层) ======================
+ // 【新增】防重复触发:3秒内只允许一次播报(关键修复)
+ private static final long MIN_TRIGGER_INTERVAL = 3000;
+ private static long sLastTriggerTime = 0;
+
+ // ====================== 成员变量 ======================
private WeakReference mwrControlCenterService;
- private boolean isRegistered = false; // 标记广播注册状态,避免冗余操作
+ private boolean isRegistered = false;
- // ====================== 构造方法(初始化弱引用,避免服务强引用泄漏) ======================
+ // ====================== 构造 ======================
public ControlCenterServiceReceiver(ControlCenterService service) {
- LogUtils.d(TAG, String.format("ControlCenterServiceReceiver() 构造 | 服务实例:%s",
+ LogUtils.d(TAG, String.format("ControlCenterServiceReceiver() 构造 | 服务:%s",
service != null ? service.getClass().getSimpleName() : "null"));
- this.mwrControlCenterService = new WeakReference(service);
+ this.mwrControlCenterService = new WeakReference<>(service);
}
- // ====================== 广播核心接收逻辑(入口方法,分Action分发处理) ======================
+ // ====================== 广播入口 ======================
@Override
public void onReceive(Context context, Intent intent) {
String action = intent != null ? intent.getAction() : "null";
- LogUtils.d(TAG, String.format("onReceive() 执行 | 接收广播 Action:%s", action));
+ LogUtils.d(TAG, String.format("onReceive() | Action=%s", action));
- // 基础参数校验
if (context == null || intent == null || action == null) {
- LogUtils.e(TAG, "onReceive() 终止 | 参数无效(context=" + context + " | intent=" + intent + ")");
+ LogUtils.e(TAG, "onReceive() 终止:参数无效");
return;
}
- // 弱引用获取服务,双重校验服务有效性
ControlCenterService service = mwrControlCenterService != null ? mwrControlCenterService.get() : null;
if (service == null || service.isDestroyed()) {
- LogUtils.e(TAG, "onReceive() 终止 | 服务已销毁或为空,执行注销");
+ LogUtils.e(TAG, "onReceive() 终止:服务已销毁");
unregisterAction(context);
return;
}
- // 分Action处理业务逻辑
switch (action) {
case Intent.ACTION_BATTERY_CHANGED:
handleBatteryStateChanged(service, intent);
@@ -82,192 +81,145 @@ public class ControlCenterServiceReceiver extends BroadcastReceiver {
handleUpdateForegroundNotification(service);
break;
case ACTION_APPCONFIG_CHANGED:
- LogUtils.d(TAG, "onReceive() 分发 | 处理配置更新广播");
handleNotifyAppConfigUpdate(service);
break;
default:
- LogUtils.w(TAG, String.format("onReceive() 警告 | 未知Action=%s", action));
+ LogUtils.w(TAG, "未知Action:" + action);
}
-
- LogUtils.d(TAG, "onReceive() 完成 | 广播处理结束");
}
- // ====================== 业务处理方法(按功能拆分,强化容错与日志) ======================
- /**
- * 处理电池状态变化广播
- * @param service 控制中心服务实例
- * @param intent 电池状态广播意图
- */
+ // ====================== 电池状态(核心修复区) ======================
private void handleBatteryStateChanged(ControlCenterService service, Intent intent) {
- LogUtils.d(TAG, "handleBatteryStateChanged() 执行 | 解析电池状态");
+ LogUtils.d(TAG, "handleBatteryStateChanged() 解析电池状态");
try {
- // 1. 解析并校验当前电池状态
boolean currentCharging = BatteryUtils.isCharging(intent);
int currentBatteryLevel = BatteryUtils.getCurrentBatteryLevel(intent);
currentBatteryLevel = Math.min(Math.max(currentBatteryLevel, BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
- LogUtils.d(TAG, String.format("handleBatteryStateChanged() 解析 | 充电=%b | 电量=%d%%", currentCharging, currentBatteryLevel));
- // 2. 状态无变化则跳过,减少无效运算
+ LogUtils.d(TAG, String.format("当前:充电=%b | 电量=%d%%", currentCharging, currentBatteryLevel));
+
+ // ================ 【关键修复1】状态没变直接跳过 ================
if (currentCharging == sIsCharging && currentBatteryLevel == sLastBatteryLevel) {
- LogUtils.d(TAG, "handleBatteryStateChanged() 跳过 | 电池状态无变化");
+ LogUtils.d(TAG, "状态无变化,跳过");
return;
}
-
- // 在插拔充电线时,执行贴心服务
- if(currentCharging != sIsCharging && sLastBatteryLevel != INVALID_BATTERY) {
- //App.notifyMessage(TAG, String.format("sLastBatteryLevel %d", sLastBatteryLevel));
- if(currentCharging) {
- ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.CHARGE_STATE);
- } else {
- ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.DISCHARGE_STATE);
- }
- }
- // 3. 更新静态缓存状态,保证多线程可见
+ // ================ 【关键修复2】只有 真正插拔充电 才播报 ================
+ boolean isRealPlugSwitch = (currentCharging != sIsCharging);
+ boolean isBatteryValid = (sLastBatteryLevel != INVALID_BATTERY);
+
+ // ================ 【关键修复3】防抖动:3秒内不重复触发 ================
+ long now = System.currentTimeMillis();
+ boolean canTrigger = (now - sLastTriggerTime >= MIN_TRIGGER_INTERVAL);
+
+ if (isRealPlugSwitch && isBatteryValid && canTrigger) {
+ LogUtils.d(TAG, "检测到充电状态切换 → 执行TTS提醒");
+
+ // 更新触发时间
+ sLastTriggerTime = now;
+
+ // 执行播报
+ if (currentCharging) {
+ ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.CHARGE_STATE);
+ } else {
+ ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.DISCHARGE_STATE);
+ }
+ }
+
+ // 更新缓存
sIsCharging = currentCharging;
sLastBatteryLevel = currentBatteryLevel;
- // 4. 同步缓存状态到配置
+ // 同步配置
handleNotifyAppConfigUpdate(service);
- LogUtils.d(TAG, String.format("handleBatteryStateChanged() 完成 | 缓存电量=%d%% | 缓存充电状态=%b",
- sLastBatteryLevel, sIsCharging));
} catch (Exception e) {
- LogUtils.e(TAG, "handleBatteryStateChanged() 失败", e);
+ LogUtils.e(TAG, "handleBatteryStateChanged 异常", e);
}
}
- /**
- * 处理配置变更通知,同步缓存状态到配置
- * @param service 控制中心服务实例
- */
+ // ====================== 配置同步 ======================
private void handleNotifyAppConfigUpdate(ControlCenterService service) {
- LogUtils.d(TAG, "handleNotifyAppConfigUpdate() 执行 | 同步缓存状态到配置");
+ LogUtils.d(TAG, "handleNotifyAppConfigUpdate() 同步配置");
try {
- // 加载最新配置
AppConfigBean latestConfig = AppConfigUtils.getInstance(service).loadAppConfig();
if (latestConfig == null) {
- LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
+ LogUtils.e(TAG, "配置为空,终止");
return;
}
- LogUtils.d(TAG, String.format("handleNotifyAppConfigUpdate() 加载 | 充电阈值=%d | 耗电阈值=%d",
- latestConfig.getChargeReminderValue(), latestConfig.getUsageReminderValue()));
- // 同步缓存的电池状态到配置
App.sQuantityOfElectricity = sLastBatteryLevel;
latestConfig.setIsCharging(sIsCharging);
service.notifyAppConfigUpdate(latestConfig);
- LogUtils.d(TAG, String.format("handleNotifyAppConfigUpdate() 完成 | 缓存电量=%d%% | 充电状态=%b",
- sLastBatteryLevel, sIsCharging));
} catch (Exception e) {
- LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 失败", e);
+ LogUtils.e(TAG, "handleNotifyAppConfigUpdate 异常", e);
}
}
- /**
- * 处理前台服务通知更新
- * @param service 控制中心服务实例
- */
+ // ====================== 通知更新 ======================
private void handleUpdateForegroundNotification(ControlCenterService service) {
- LogUtils.d(TAG, "handleUpdateForegroundNotification() 执行 | 更新前台通知");
+ LogUtils.d(TAG, "handleUpdateForegroundNotification() 更新通知");
try {
NotificationManagerUtils notifyUtils = service.getNotificationManager();
NotificationMessage notifyMsg = service.getForegroundNotifyMsg();
- // 非空校验,避免空指针
if (notifyUtils == null || notifyMsg == null) {
- LogUtils.e(TAG, String.format("handleUpdateForegroundNotification() 终止 | 通知工具类或消息为空(notifyUtils=%s | notifyMsg=%s)",
- notifyUtils, notifyMsg));
+ LogUtils.e(TAG, "通知工具或消息为空");
return;
}
-
notifyUtils.updateForegroundServiceNotify(notifyMsg);
- LogUtils.d(TAG, String.format("handleUpdateForegroundNotification() 完成 | 标题=%s", notifyMsg.getTitle()));
} catch (Exception e) {
- LogUtils.e(TAG, "handleUpdateForegroundNotification() 失败", e);
+ LogUtils.e(TAG, "更新通知异常", e);
}
}
- // ====================== 广播注册/注销(强化容错,避免重复操作) ======================
- /**
- * 注册广播接收器
- * @param context 上下文
- */
+ // ====================== 注册/注销 ======================
public void registerAction(Context context) {
- LogUtils.d(TAG, "registerAction() 执行 | 注册广播接收器");
- if (context == null || isRegistered) {
- LogUtils.e(TAG, "registerAction() 失败 | 上下文为空或已注册");
- return;
- }
-
+ if (context == null || isRegistered) return;
try {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(ACTION_UPDATE_FOREGROUND_NOTIFICATION);
filter.addAction(ACTION_APPCONFIG_CHANGED);
filter.setPriority(BROADCAST_PRIORITY);
-
context.registerReceiver(this, filter);
isRegistered = true;
- LogUtils.d(TAG, String.format("registerAction() 完成 | 优先级=%d", BROADCAST_PRIORITY));
+ LogUtils.d(TAG, "广播注册完成");
} catch (Exception e) {
- LogUtils.e(TAG, "registerAction() 失败", e);
+ LogUtils.e(TAG, "注册异常", e);
}
}
- /**
- * 注销广播接收器
- * @param context 上下文
- */
public void unregisterAction(Context context) {
- LogUtils.d(TAG, "unregisterAction() 执行 | 注销广播接收器");
- if (context == null || !isRegistered) {
- LogUtils.e(TAG, "unregisterAction() 失败 | 上下文为空或未注册");
- return;
- }
-
+ if (context == null || !isRegistered) return;
try {
context.unregisterReceiver(this);
isRegistered = false;
- LogUtils.d(TAG, "unregisterAction() 完成 | 广播注销成功");
- } catch (IllegalArgumentException e) {
- LogUtils.w(TAG, "unregisterAction() 警告 | 广播未注册,跳过注销");
+ LogUtils.d(TAG, "广播已注销");
} catch (Exception e) {
- LogUtils.e(TAG, "unregisterAction() 失败", e);
+ LogUtils.w(TAG, "注销异常", e);
}
}
- // ====================== 资源释放与Getter方法(按需开放,防泄漏) ======================
- /**
- * 主动释放资源,避免内存泄漏
- */
+ // ====================== 释放 ======================
public void release() {
- LogUtils.d(TAG, "release() 执行 | 释放广播接收器资源");
- // 清空弱引用,帮助GC回收
+ LogUtils.d(TAG, "release() 释放资源");
if (mwrControlCenterService != null) {
mwrControlCenterService.clear();
mwrControlCenterService = null;
- LogUtils.d(TAG, "release() 步骤 | 弱引用已清空");
}
- // 重置静态状态缓存
- sLastBatteryLevel = -1;
+ // 重置静态状态
+ sLastBatteryLevel = INVALID_BATTERY;
sIsCharging = false;
- LogUtils.d(TAG, "release() 完成 | 静态状态缓存已重置");
+ sLastTriggerTime = 0; // 【新增】重置防抖时间
}
- /**
- * 获取上次记录的电池电量
- * @return 电量值(0-100),未初始化返回-1
- */
+ // ====================== Getter ======================
public static int getLastBatteryLevel() {
return sLastBatteryLevel;
}
- /**
- * 获取上次记录的充电状态
- * @return true=充电中,false=未充电
- */
public static boolean isLastCharging() {
return sIsCharging;
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ThoughtfulService.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ThoughtfulService.java
index 5a257ef..4a3343d 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ThoughtfulService.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ThoughtfulService.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
@@ -18,147 +19,203 @@ import cc.winboll.studio.powerbell.utils.AppConfigUtils;
*/
public class ThoughtfulService extends Service {
- // ====================================== 常量区 - 置顶排序 ======================================
+ // ====================================== 常量区 ======================================
public static final String TAG = "ThoughtfulService";
- /** Intent传递 服务类型 的Key值 */
public static final String EXTRA_SERVICE_TYPE = "EXTRA_SERVICE_TYPE";
- // ====================================== 枚举类 - 服务类型 充电/放电状态 ======================================
- /**
- * 服务执行类型枚举
- * CHARGE_STATE : 充电状态服务
- * DISCHARGE_STATE : 放电(耗电)状态服务
- */
+ // 防止重复播报的标志(静态,全局唯一)
+ private static boolean sIsPlaying = false;
+
+ // ====================================== 枚举 ======================================
public enum ServiceType {
- CHARGE_STATE, //充电状态服务
- DISCHARGE_STATE //放电状态服务
+ CHARGE_STATE,
+ DISCHARGE_STATE
}
- // ====================================== 对外公开静态启动函数【新增核心】入参Context + 枚举 ======================================
- /**
- * 公开静态方法:传入上下文+服务类型枚举,一键构建意图并启动当前服务
- * @param context 上下文对象
- * @param serviceType 服务类型枚举【充电/放电】
- */
+ // ====================================== 外部启动入口(加固) ======================================
public static void startServiceWithType(Context context, ServiceType serviceType) {
- LogUtils.d(TAG, "【startServiceWithType】静态启动方法调用 | Context=" + context + " | ServiceType=" + (serviceType == null ? "null" : serviceType.name()));
-
- ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(context, ThoughtfulServiceBean.class);
- if (thoughtfulServiceBean == null) {
- thoughtfulServiceBean = new ThoughtfulServiceBean();
- }
-
- // 对应TTS服务提醒没有启用,就退出
- if((serviceType == ServiceType.CHARGE_STATE && !thoughtfulServiceBean.isEnableChargeTts())
- ||(serviceType == ServiceType.DISCHARGE_STATE && !thoughtfulServiceBean.isEnableUsePowerTts())){
- return;
- }
-
-
- // 判空健壮性校验
- if (context != null && serviceType != null) {
- // 构建意图 + 封装枚举参数
- Intent intent = new Intent(context, ThoughtfulService.class);
- intent.putExtra(EXTRA_SERVICE_TYPE, serviceType);
- // 启动服务
- context.startService(intent);
- LogUtils.d(TAG, "【startServiceWithType】服务启动成功,执行[" + serviceType.name() + "]任务");
- } else {
- LogUtils.d(TAG, "【startServiceWithType】上下文为空 或 服务类型枚举为空,跳过启动服务");
+ LogUtils.d(TAG, "【startServiceWithType】调用 | type=" + (serviceType == null ? "null" : serviceType.name()));
+
+ if (context == null || serviceType == null) {
+ LogUtils.d(TAG, "【startServiceWithType】空参数,直接返回");
+ return;
}
+
+ // 1. 预先读取配置,不满足直接不启动服务(最关键拦截)
+ ThoughtfulServiceBean ttsBean = ThoughtfulServiceBean.loadBean(context, ThoughtfulServiceBean.class);
+ if (ttsBean == null) {
+ ttsBean = new ThoughtfulServiceBean();
+ }
+
+ // 充电TTS未开 → 不启动
+ if (serviceType == ServiceType.CHARGE_STATE && !ttsBean.isEnableChargeTts()) {
+ LogUtils.d(TAG, "【startServiceWithType】充电TTS未启用,不启动服务");
+ return;
+ }
+
+ // 用电TTS未开 → 不启动
+ if (serviceType == ServiceType.DISCHARGE_STATE && !ttsBean.isEnableUsePowerTts()) {
+ LogUtils.d(TAG, "【startServiceWithType】用电TTS未启用,不启动服务");
+ return;
+ }
+
+ // 2. 防止重复启动导致叠加播报
+ if (sIsPlaying) {
+ LogUtils.d(TAG, "【startServiceWithType】已有播报任务,跳过本次启动");
+ return;
+ }
+
+ // 3. 真正启动
+ Intent intent = new Intent(context, ThoughtfulService.class);
+ intent.putExtra(EXTRA_SERVICE_TYPE, serviceType);
+ context.startService(intent);
+ LogUtils.d(TAG, "【startServiceWithType】服务启动成功");
}
- // ====================================== 生命周期方法 - 绑定服务 (原逻辑保留) ======================================
+ // ====================================== 生命周期 ======================================
@Override
public IBinder onBind(Intent intent) {
- LogUtils.d(TAG, "【onBind】服务绑定方法调用,入参Intent:" + intent);
return null;
}
- // ====================================== 生命周期方法 - 启动服务【核心逻辑】接收枚举+分支执行任务 ======================================
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- LogUtils.d(TAG, "【onStartCommand】服务启动方法调用 | intent=" + intent + " | flags=" + flags + " | startId=" + startId);
- // 判断意图非空,解析服务类型参数
- if (intent != null) {
- LogUtils.d(TAG, "【onStartCommand】Intent不为空,开始解析服务类型枚举参数");
- // 获取传递的服务类型枚举
- ServiceType serviceType = (ServiceType) intent.getSerializableExtra(EXTRA_SERVICE_TYPE);
- // 根据服务类型,执行对应任务
- if (serviceType != null) {
- LogUtils.d(TAG, "【onStartCommand】解析到服务类型:" + serviceType.name());
- switch (serviceType) {
- case CHARGE_STATE:
- // 执行【充电状态】对应的业务任务
- executeChargeStateTask();
- break;
- case DISCHARGE_STATE:
- // 执行【放电状态】对应的业务任务
- executeDischargeStateTask();
- break;
- default:
- LogUtils.d(TAG, "【onStartCommand】未知的服务类型,不执行任何任务");
- break;
- }
- } else {
- LogUtils.d(TAG, "【onStartCommand】未解析到有效服务类型参数,参数为空");
- }
- } else {
- LogUtils.d(TAG, "【onStartCommand】启动服务的Intent为空,直接返回");
+ LogUtils.d(TAG, "【onStartCommand】进入");
+
+ if (intent == null) {
+ LogUtils.d(TAG, "【onStartCommand】intent = null,停止");
+ stopSelf();
+ return START_NOT_STICKY;
}
- // 返回默认策略,与原生逻辑一致
- int result = super.onStartCommand(intent, flags, startId);
- LogUtils.d(TAG, "【onStartCommand】服务执行完成,返回值:" + result);
- return result;
+ // 再次防止重复播报(双重保险)
+ if (sIsPlaying) {
+ LogUtils.d(TAG, "【onStartCommand】已有播报,直接停止服务");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ // 解析类型
+ ServiceType type = (ServiceType) intent.getSerializableExtra(EXTRA_SERVICE_TYPE);
+ if (type == null) {
+ LogUtils.d(TAG, "【onStartCommand】type = null,停止");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ // 二次校验开关(防止外部绕过 startServiceWithType 直接启动)
+ ThoughtfulServiceBean ttsBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
+ if (ttsBean == null) ttsBean = new ThoughtfulServiceBean();
+
+ boolean allowPlay = false;
+ if (type == ServiceType.CHARGE_STATE && ttsBean.isEnableChargeTts()) {
+ allowPlay = true;
+ }
+ if (type == ServiceType.DISCHARGE_STATE && ttsBean.isEnableUsePowerTts()) {
+ allowPlay = true;
+ }
+
+ if (!allowPlay) {
+ LogUtils.d(TAG, "【onStartCommand】TTS开关已关闭,不执行播报");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ // 执行任务
+ if (type == ServiceType.CHARGE_STATE) {
+ executeChargeStateTask();
+ } else if (type == ServiceType.DISCHARGE_STATE) {
+ executeDischargeStateTask();
+ }
+
+ return START_NOT_STICKY; // 重要:执行完自动销毁,不保留服务
}
- // ====================================== 私有业务方法 充电/放电 分任务执行 ======================================
- /**
- * 执行【充电状态】的业务任务
- * 可在此方法内编写 充电时的逻辑(语音提醒/电量监控/弹窗等)
- */
+ // ====================================== 充电任务 ======================================
private void executeChargeStateTask() {
- LogUtils.d(TAG, "【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
- //ToastUtils.show("【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
- // TODO 此处添加充电状态需要执行的业务逻辑代码
- // 加载最新配置
- AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
- if (latestConfig == null) {
- LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
- return;
- }
-
- if (latestConfig.isEnableChargeReminder()) {
- int nChargeReminderValue = latestConfig.getChargeReminderValue();
- String szRemind = String.format("限量充电提醒已启用,限量值为百分之%d。", nChargeReminderValue);
- szRemind = szRemind + szRemind + szRemind;
- TTSPlayService.startPlayTTS(this, szRemind);
- }
+ LogUtils.d(TAG, "【executeChargeStateTask】执行充电任务");
+
+ sIsPlaying = true; // 锁定播报
+
+ try {
+ AppConfigBean config = AppConfigUtils.getInstance(this).loadAppConfig();
+ if (config == null) {
+ LogUtils.e(TAG, "配置为空,停止");
+ return;
+ }
+
+ ThoughtfulServiceBean ttsBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
+ if (ttsBean == null) ttsBean = new ThoughtfulServiceBean();
+
+ // 主提醒开关未开 → 直接不播
+ if (!config.isEnableChargeReminder()) {
+ LogUtils.d(TAG, "充电提醒总开关关闭,不播报");
+ return;
+ }
+
+ int limit = config.getChargeReminderValue();
+ int battery = App.sQuantityOfElectricity;
+
+ // 电量无效值 → 不拼接电量
+ String batteryStr = "";
+ if (battery >= 0 && battery <= 100 && ttsBean.isEnableChargeTtsWithBattary()) {
+ batteryStr = String.format("当前电量百分之%d。", battery);
+ }
+
+ String text = batteryStr + String.format("限量充电提醒已启用,限值百分之%d。", limit);
+ TTSPlayService.startPlayTTS(this, text);
+ LogUtils.d(TAG, "充电TTS已下发:" + text);
+
+ } finally {
+ sIsPlaying = false; // 释放锁
+ stopSelf();
+ }
}
- /**
- * 执行【放电(耗电)状态】的业务任务
- * 可在此方法内编写 放电时的逻辑(语音提醒/电量监控/弹窗等)
- */
+ // ====================================== 放电任务 ======================================
private void executeDischargeStateTask() {
- LogUtils.d(TAG, "【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
- //ToastUtils.show("【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
- // TODO 此处添加放电状态需要执行的业务逻辑代码
- // 加载最新配置
- AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
- if (latestConfig == null) {
- LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
- return;
- }
+ LogUtils.d(TAG, "【executeDischargeStateTask】执行放电任务");
- if (latestConfig.isEnableUsageReminder()) {
- int nUsageReminderValue = latestConfig.getUsageReminderValue();
- String szRemind = String.format("电量不足提醒已启用,低电值为百分之%d。", nUsageReminderValue);
- //szRemind = szRemind + szRemind + szRemind;
- TTSPlayService.startPlayTTS(this, szRemind);
- }
+ sIsPlaying = true;
+
+ try {
+ AppConfigBean config = AppConfigUtils.getInstance(this).loadAppConfig();
+ if (config == null) {
+ LogUtils.e(TAG, "配置为空,停止");
+ return;
+ }
+
+ ThoughtfulServiceBean ttsBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
+ if (ttsBean == null) ttsBean = new ThoughtfulServiceBean();
+
+ // 主提醒开关未开 → 不播
+ if (!config.isEnableUsageReminder()) {
+ LogUtils.d(TAG, "低电提醒总开关关闭,不播报");
+ return;
+ }
+
+ int limit = config.getUsageReminderValue();
+ int battery = App.sQuantityOfElectricity;
+
+ String batteryStr = "";
+ if (battery >= 0 && battery <= 100 && ttsBean.isEnableUseageTtsWithBattary()) {
+ batteryStr = String.format("当前电量百分之%d。", battery);
+ }
+
+ String text = batteryStr + String.format("低电量提醒已启用,限值百分之%d。", limit);
+ TTSPlayService.startPlayTTS(this, text);
+ LogUtils.d(TAG, "放电TTS已下发:" + text);
+
+ } finally {
+ sIsPlaying = false;
+ stopSelf();
+ }
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LogUtils.d(TAG, "【onDestroy】服务已销毁");
+ }
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java
index ad05f6f..07a448a 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java
@@ -188,6 +188,9 @@ public class RemindThread extends Thread {
LogUtils.d(TAG, String.format("未有合适类型提醒,退出提醒线程 | threadId=%d", getId()));
break;
}
+
+ // 启动电量通知 TTS 语音提醒线程
+ TTSRemindThread.start(this.mContext, App.sQuantityOfElectricity, isCharging);
// 安全休眠,保留中断标记
safeSleepInternal(sleepTime);
@@ -280,6 +283,7 @@ public class RemindThread extends Thread {
*/
private void cleanThreadStateInternal() {
LogUtils.d(TAG, String.format("cleanThreadStateInternal() 调用 | threadId=%d", getId()));
+ TTSRemindThread.stopTTS();
isReminding = false;
isExist = true;
// 中断当前线程(如果存活)
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/TTSRemindThread.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/TTSRemindThread.java
new file mode 100644
index 0000000..714e0bb
--- /dev/null
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/TTSRemindThread.java
@@ -0,0 +1,122 @@
+package cc.winboll.studio.powerbell.threads;
+
+import android.content.Context;
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libappbase.ToastUtils;
+import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
+import cc.winboll.studio.powerbell.services.TTSPlayService;
+
+/**
+ * @Author 豆包&ZhanGSKen
+ * @Date 2026/02/28 20:41
+ * @Describe TTS 语音通知线程(单例 + 继承 Thread)
+ */
+public class TTSRemindThread extends Thread {
+
+ public static final String TAG = "TTSRemindThread";
+
+ // 单例实例
+ private static TTSRemindThread sInstance;
+
+ // 运行标志
+ private volatile boolean mIsRunning = false;
+ private volatile int mBattery;
+ private volatile boolean mIsCharging;
+
+ private Context mContext;
+
+ /**
+ * 私有构造,单例禁用外部 new
+ */
+ private TTSRemindThread(Context context) {
+ this.mContext = context.getApplicationContext();
+ }
+
+ /**
+ * 获取单例(DCL 双重校验)
+ */
+ public static TTSRemindThread getInstance(Context context) {
+ if (sInstance == null) {
+ synchronized (TTSRemindThread.class) {
+ if (sInstance == null) {
+ sInstance = new TTSRemindThread(context);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * 对外静态启动入口
+ */
+ public static void start(Context context, int battery, boolean isCharging) {
+ TTSRemindThread instance = getInstance(context);
+ instance.mBattery = battery;
+ instance.mIsCharging = isCharging;
+ if (!instance.mIsRunning) {
+ LogUtils.d(TAG, "start() TTS 提醒线程启动");
+ instance.mIsRunning = true;
+ instance.start(); // 启动线程
+ }
+ }
+
+ /**
+ * 对外静态停止入口
+ */
+ public static void stopTTS() {
+ if (sInstance != null) {
+ LogUtils.d(TAG, "stopTTS() TTS 提醒线程停止");
+ sInstance.mIsRunning = false;
+ sInstance = null;
+ }
+ }
+
+ /**
+ * 线程主逻辑
+ */
+ @Override
+ public void run() {
+ super.run();
+ LogUtils.d(TAG, "run() TTS 线程已开始循环");
+
+ while (mIsRunning) {
+ try {
+ // ======================
+ // 在这里写你的循环 TTS 逻辑
+ // ======================
+
+ // TTS 语音通知模块
+ // 读取 TTS 语音通知配置
+ ThoughtfulServiceBean ttsBean = ThoughtfulServiceBean.loadBean(mContext, ThoughtfulServiceBean.class);
+ if (ttsBean == null) {
+ ttsBean = new ThoughtfulServiceBean();
+ }
+ if (ttsBean.isEnableTtsWhenNotifyBattery()) {
+ //ToastUtils.show("Test");
+ // 启动
+ //ToastUtils.show(String.format("mIsCharging %s, mBattery %d", mIsCharging, mBattery));
+ String text = mIsCharging ?"充电": "用电";
+ text += String.format("已达预定值,现在电量为百分之%d", mBattery);
+ TTSPlayService.startPlayTTS(mContext, text);
+ }
+
+ // 防止死循环疯狂跑,加一点休眠
+ sleep(6000);
+ } catch (InterruptedException e) {
+ LogUtils.e(TAG, "TTS 线程被中断", e);
+ break;
+ }
+ }
+
+ mIsRunning = false;
+ LogUtils.d(TAG, "run() TTS 线程已退出");
+ }
+
+ /**
+ * 是否正在运行
+ */
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+}
+
diff --git a/powerbell/src/main/res/layout/activity_about.xml b/powerbell/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..3f84f0a
--- /dev/null
+++ b/powerbell/src/main/res/layout/activity_about.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/powerbell/src/main/res/layout/activity_settings.xml b/powerbell/src/main/res/layout/activity_settings.xml
index 0b693e7..029bdac 100644
--- a/powerbell/src/main/res/layout/activity_settings.xml
+++ b/powerbell/src/main/res/layout/activity_settings.xml
@@ -62,6 +62,33 @@
android:onClick="onEnableUsePowerTts"
android:id="@+id/activitysettingsCheckBox1"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+