Compare commits

...

17 Commits

Author SHA1 Message Date
ZhanGSKen 000afbe971 <powerbell>APK 15.15.26 release Publish. 2026-07-18 10:40:59 +08:00
ZhanGSKen be3b9360dd 更新源码仓库地址 2026-07-18 10:39:21 +08:00
ZhanGSKen 80da1c63a6 <powerbell>APK 15.15.25 release Publish. 2026-07-18 10:27:47 +08:00
MiMo 75c790417c <powerbell> 优化 ChargeMailNotify 悬浮窗功能
1. 悬浮窗新增点击事件
   - 点击悬浮窗打开应用主窗口 MainActivity
   - 打开主窗口后保持悬浮窗显示

2. 悬浮窗新增应用标题
   - 显示 strings.xml 中的 app_name 字符串
   - 标题样式加粗16sp,与内容区分
2026-07-18 10:26:10 +08:00
ZhanGSKen 25a9e4b230 <powerbell>APK 15.15.24 release Publish. 2026-07-18 10:07:32 +08:00
MiMo 1de0a36d79 <powerbell> 优化 ChargeMailNotify 提醒方式切换逻辑
1. RemindThread 提醒线程
   - ChargeMailNotify 启用:使用 APPMSGMailRemindThread 邮件提醒
   - ChargeMailNotify 未启用:使用 TTSRemindThread 语音提醒
   - 停止线程时同步切换逻辑

2. ControlCenterServiceHandler 消息处理
   - ChargeMailNotify 启用:使用 Toast 代替通知栏提醒
   - ChargeMailNotify 未启用:正常发送通知栏提醒

3. 悬浮窗提示信息更新
   - 新增TTS语音播放功能已禁用提示
   - 新增电量提醒通知栏功能已禁用提示
2026-07-18 10:05:08 +08:00
ZhanGSKen f7968e55c6 <powerbell>APK 15.15.23 release Publish. 2026-07-18 05:24:20 +08:00
MiMo 97f42054d2 <powerbell> 优化 ChargeMailNotify 开关联动逻辑
1. SettingsActivity 充电邮件通知开关
   - 开启时:先 stopService 再 startService 重启 ControlCenterService
   - 关闭时:停止 RemindThread 并关闭悬浮窗

2. ControlCenterService 服务销毁
   - onDestroy 中关闭 ChargeMailNotifyFloatWindow 悬浮窗
   - 资源释放顺序:前台服务 → 线程 → 悬浮窗 → 广播接收器 → Handler → 通知
2026-07-18 05:21:48 +08:00
ZhanGSKen 184ea4fd71 <powerbell>APK 15.15.22 release Publish. 2026-07-18 04:46:59 +08:00
MiMo 526fa6c241 - 更新 libaes 依赖至 15.20.21
- 新增 JavaMail/SMTP ProGuard 混淆规则,防止 R8 混淆导致 SMTP 邮件发送失败
- 保留 javax.mail、com.sun.mail 等核心邮件类不被混淆
2026-07-18 04:44:37 +08:00
MiMo a038a2db97 <powerbell>APK 15.15.20 release Publish. 2026-07-18 03:20:47 +08:00
ZhanGSKen 526ca1e926 <powerbell>APK 15.15.19 release Publish. 2026-07-17 22:37:50 +08:00
MiMo e1428632e6 <powerbell> 新增 ChargeMailNotify 功能开关及悬浮窗提示
1. 新增 ChargeMailNotifyHelper 功能开关管理类
   - 使用 SharedPreferences 持久化存储开关状态
   - 提供静态方法 isEnabled() / setEnabled()

2. 新增 ChargeMailNotifyFloatWindow 悬浮窗工具类
   - 单例模式(DCL双重检查锁)
   - show() 显示悬浮窗(带邮件图标)
   - dismissWindow() 静态方法供外部关闭悬浮窗
   - 悬浮窗持续显示,仅在开关关闭时关闭

3. 修改 SettingsActivity 设置界面
   - 新增充电邮件通知 Switch 开关(位于 SMTPConfigView 上方)
   - Switch 关闭时:禁用 SMTPConfigView、停止 RemindThread、关闭悬浮窗
   - Switch 打开时:启用 SMTPConfigView

4. 修改 ControlCenterService 服务
   - 服务启动时检查 ChargeMailNotify 功能状态
   - 启用时显示悬浮窗提示充电邮件通知服务已启用

5. 修改 RemindThread 提醒线程
   - 启动/停止 APPMSGMailRemindThread 前检查开关状态
   - 仅在 ChargeMailNotify 启用时触发邮件提醒
2026-07-17 22:22:46 +08:00
MiMo 575664b2d6 <powerbell> 更新依赖版本及新增邮件提醒功能
主要改动:

1. 编译配置
   - 强制 Java 7 编译兼容性 (sourceCompatibility/targetCompatibility)
   - 添加 packagingOptions 解决 android-mail/android-activation 重复 META-INF 文件冲突

2. 依赖更新
   - libaes: 15.20.18 → 15.20.20
   - 新增 com.sun.mail:android-mail:1.6.7 (替代 javax.mail:1.6.2)
   - 新增 com.sun.mail:android-activation:1.6.7

3. 新增邮件提醒功能
   - 新增 APPMSGMailRemindThread 单例线程 (10秒间隔)
   - 新增 SMTPConfigView 邮件配置界面
   - 修改 RemindThread 同步启停 APPMSGMailRemindThread

4. 设置界面
   - SettingsActivity 添加 SMTPConfigView 绑定
   - activity_settings.xml 添加邮件配置区域
2026-07-17 19:32:14 +08:00
MiMo 3479bb0faf <powerbell> RemindThread 同步启停 APPMSGMailRemindThread,邮件间隔调整为10秒
- RemindThread 触发提醒时同步启动 APPMSGMailRemindThread
- RemindThread 线程退出时同步停止 APPMSGMailRemindThread
- APPMSGMailRemindThread 邮件发送间隔 30秒 → 10秒
2026-07-17 18:41:36 +08:00
MiMo 9f93489a71 <powerbell> 新增 APPMSGMailRemindThread 邮件提醒线程及 SMTPConfigView 配置界面
- 新增 APPMSGMailRemindThread 单实例线程,定时30秒发送电量状态邮件
- SettingsActivity 添加 SMTPConfigView 控件,支持 SMTP 配置与测试发送
- build.gradle 添加 android-mail/android-activation 1.6.7 依赖
- build.gradle 添加 packagingOptions 排除重复 META-INF 文件
- build.properties: 构建计数器自动更新
2026-07-17 17:57:05 +08:00
MiMo 2747b074ad <powerbell> 强制 Java 7 编译兼容性,更新 libaes 至 15.20.19
- build.gradle: 新增 compileOptions 配置 Java 7 兼容性
- build.gradle: 更新 cc.winboll.studio:libaes 15.20.18 → 15.20.19
- build.properties: 构建计数器自动更新
2026-07-17 16:01:56 +08:00
13 changed files with 584 additions and 29 deletions
+15 -2
View File
@@ -21,6 +21,12 @@ android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
// 确保 Java 7 兼容性(已适配项目技术栈)
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
defaultConfig {
applicationId "cc.winboll.studio.powerbell"
@@ -36,7 +42,10 @@ android {
}
}
packagingOptions {
exclude 'META-INF/NOTICE.md'
exclude 'META-INF/LICENSE.md'
}
}
dependencies {
@@ -75,7 +84,7 @@ dependencies {
//api 'androidx.fragment:fragment:1.1.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.20.18'
api 'cc.winboll.studio:libaes:15.20.21'
api 'cc.winboll.studio:libappbase:15.20.34'
// WinBoLL备用库 jitpack.io 地址
@@ -84,4 +93,8 @@ dependencies {
//api fileTree(dir: 'libs', include: ['*.aar'])
api fileTree(dir: 'libs', include: ['*.jar'])
// JavaMailAPPMSGMailUtils 发送邮件依赖,Android 专用版)
implementation 'com.sun.mail:android-mail:1.6.7'
implementation 'com.sun.mail:android-activation:1.6.7'
}
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Jul 12 10:33:40 HKT 2026
stageCount=19
#Sat Jul 18 10:40:59 HKT 2026
stageCount=27
libraryProject=
baseVersion=15.15
publishVersion=15.15.18
publishVersion=15.15.26
buildCount=0
baseBetaVersion=15.15.19
baseBetaVersion=15.15.27
+16
View File
@@ -60,6 +60,22 @@
-dontwarn java.util.function.**
# ============================== 第三方框架专项规则 ==============================
# JavaMail / SMTP 邮件发送(APPMSGMailUtils 依赖,防止 R8 混淆导致 SMTP 发送失败)
-keep class javax.mail.** { *; }
-keep class javax.mail.internet.** { *; }
-keep class javax.activation.** { *; }
-keep class com.sun.mail.** { *; }
-keep class com.sun.mail.util.** { *; }
-keep class com.sun.mail.smtp.** { *; }
-keep class com.sun.mail.pop3.** { *; }
-keep class com.sun.mail.imap.** { *; }
-dontwarn javax.mail.**
-dontwarn javax.activation.**
-dontwarn com.sun.mail.**
-dontwarn java.awt.**
-dontwarn javax.naming.**
-dontwarn javax.net.ssl.**
# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容)
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
@@ -68,7 +68,7 @@ public class AboutActivity extends WinBoLLActivity {
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("MyWinBoLL");
appInfo.setAppGitName("PowerBell");
appInfo.setAppGitOwner("ZhanGSKen");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
@@ -14,10 +14,16 @@ import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.SMTPConfigView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
import cc.winboll.studio.powerbell.utils.ChargeMailNotifyHelper;
import cc.winboll.studio.powerbell.utils.ChargeMailNotifyFloatWindow;
import cc.winboll.studio.powerbell.threads.RemindThread;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import android.widget.Switch;
import java.lang.reflect.Field;
/**
@@ -40,6 +46,8 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
private CheckBox cbUseageTtsBattary; // 用电TTS带电量提醒(子开关)
private CheckBox cbChargeTtsBattary; // 充电TTS带电量提醒(子开关)
private CheckBox cbTtsWhenNotifyBattery; // 👉 新增:通知电量时同时播放TTS
private Switch switchChargeMailNotify; // 充电邮件通知主开关
private SMTPConfigView smtpConfigView; // SMTP邮件配置视图
// ======================== 接口实现区 =========================
@Override
@@ -62,6 +70,8 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
initToolbar();
initTtsCheckBoxes();
initTtsCheckBoxStatus();
initSMTPConfigView();
initChargeMailNotifySwitch();
LogUtils.d(TAG, "onCreate: 应用设置页面初始化完成");
}
@@ -126,6 +136,50 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
LogUtils.d(TAG, "initTtsCheckBoxStatus: TTS复选框状态初始化完成");
}
/**
* 初始化充电邮件通知开关
*/
private void initChargeMailNotifySwitch() {
LogUtils.d(TAG, "initChargeMailNotifySwitch: 充电邮件通知开关初始化开始");
switchChargeMailNotify = findViewById(R.id.switchChargeMailNotify);
boolean enabled = ChargeMailNotifyHelper.isEnabled(this);
switchChargeMailNotify.setChecked(enabled);
updateSMTPConfigViewEnabled(enabled);
LogUtils.d(TAG, "initChargeMailNotifySwitch: 充电邮件通知开关初始化完成,状态=" + enabled);
}
/**
* 初始化SMTP邮件配置视图
*/
private void initSMTPConfigView() {
LogUtils.d(TAG, "initSMTPConfigView: SMTP邮件配置视图初始化开始");
smtpConfigView = findViewById(R.id.smtp_config_view);
LogUtils.d(TAG, "initSMTPConfigView: SMTP邮件配置视图初始化完成");
}
/**
* 更新SMTP配置视图的启用状态
*/
private void updateSMTPConfigViewEnabled(boolean enabled) {
LogUtils.d(TAG, "updateSMTPConfigViewEnabled: 设置SMTPConfigView启用状态=" + enabled);
smtpConfigView.setEnabled(enabled);
// 递归设置子控件的启用状态
setViewAndChildrenEnabled(smtpConfigView, enabled);
}
/**
* 递归设置View及其所有子控件的启用状态
*/
private void setViewAndChildrenEnabled(View view, boolean enabled) {
view.setEnabled(enabled);
if (view instanceof android.view.ViewGroup) {
android.view.ViewGroup group = (android.view.ViewGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
setViewAndChildrenEnabled(group.getChildAt(i), enabled);
}
}
}
// ======================== 事件响应区 =========================
/**
* 悬浮窗权限检查入口
@@ -201,6 +255,29 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
LogUtils.d(TAG, "onEnableTtsWhenNotifyBattery: 保存完成");
}
/**
* 充电邮件通知开关点击事件
*/
public void onChargeMailNotifySwitchChanged(View view) {
boolean isChecked = switchChargeMailNotify.isChecked();
LogUtils.d(TAG, "onChargeMailNotifySwitchChanged: 充电邮件通知开关状态=" + isChecked);
ChargeMailNotifyHelper.setEnabled(this, isChecked);
updateSMTPConfigViewEnabled(isChecked);
if (isChecked) {
// 开启时重启ControlCenterService
LogUtils.d(TAG, "onChargeMailNotifySwitchChanged: 开启充电邮件通知,重启ControlCenterService");
stopService(new Intent(this, ControlCenterService.class));
startService(new Intent(this, ControlCenterService.class));
} else {
// 关闭时停止RemindThread并关闭悬浮窗
LogUtils.d(TAG, "onChargeMailNotifySwitchChanged: 关闭充电邮件通知,停止RemindThread");
RemindThread.stopRemindThread();
LogUtils.d(TAG, "onChargeMailNotifySwitchChanged: 关闭充电邮件通知悬浮窗");
ChargeMailNotifyFloatWindow.dismissWindow();
}
LogUtils.d(TAG, "onChargeMailNotifySwitchChanged: 状态保存完成");
}
// ======================== 工具方法区 =========================
/**
* 获取配置Bean实例,避免重复代码
@@ -3,10 +3,12 @@ package cc.winboll.studio.powerbell.handlers;
import android.os.Handler;
import android.os.Message;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
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 cc.winboll.studio.powerbell.utils.ChargeMailNotifyHelper;
import java.lang.ref.WeakReference;
/**
@@ -85,11 +87,7 @@ public class ControlCenterServiceHandler extends Handler {
private void handleRemindMessage(ControlCenterService service, String remindType, int currentBattery, boolean isCharging) {
LogUtils.d(TAG, "handleRemindMessage: 开始处理提醒消息 | type=" + remindType + " | battery=" + currentBattery + " | isCharging=" + isCharging);
// 1. 前置校验:通知工具类+参数有效性
if (service.getNotificationManager() == null) {
LogUtils.e(TAG, "handleRemindMessage: 通知管理工具类未初始化,无法发送提醒");
return;
}
// 1. 前置校验:参数有效性
if (!REMIND_TYPE_CHARGE.equals(remindType) && !REMIND_TYPE_USAGE.equals(remindType)) {
LogUtils.w(TAG, "handleRemindMessage: 提醒类型无效,忽略 | type=" + remindType + " | 允许值:" + REMIND_TYPE_CHARGE + "/" + REMIND_TYPE_USAGE);
return;
@@ -99,24 +97,40 @@ public class ControlCenterServiceHandler extends Handler {
return;
}
// 2. 构建通知模型,使用统一格式
NotificationMessage remindMsg = new NotificationMessage();
// 2. 构建提醒内容
String chargeStateDesc = isCharging ? CHARGE_STATE_CHARGING : CHARGE_STATE_NOT_CHARGING;
String content;
if (REMIND_TYPE_CHARGE.equals(remindType)) {
remindMsg.setTitle(CHARGE_REMIND_TITLE);
remindMsg.setContent(String.format(CHARGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc));
remindMsg.setRemindMSG("charge_remind");
content = String.format(CHARGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc);
} else {
remindMsg.setTitle(USAGE_REMIND_TITLE);
remindMsg.setContent(String.format(USAGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc));
remindMsg.setRemindMSG("usage_remind");
content = String.format(USAGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc);
}
LogUtils.d(TAG, "handleRemindMessage: 通知模型构建完成 | title=" + remindMsg.getTitle() + " | content=" + remindMsg.getContent());
LogUtils.d(TAG, "handleRemindMessage: 提醒内容构建完成 | content=" + content);
// 3. 调用通知工具类发送提醒
LogUtils.d(TAG, "handleRemindMessage: 调用通知工具类发送提醒 | remindMSG=" + remindMsg.getRemindMSG());
service.getNotificationManager().showRemindNotification(service, remindMsg);
LogUtils.d(TAG, "handleRemindMessage: 提醒通知发送流程执行完毕");
// 3. 根据 ChargeMailNotify 状态选择提醒方式
if (ChargeMailNotifyHelper.isEnabled(service)) {
// 启用邮件通知时,使用 Toast 代替通知栏
LogUtils.d(TAG, "handleRemindMessage: 充电邮件通知已启用,使用 Toast 提醒");
ToastUtils.show(content);
} else {
// 未启用邮件通知时,使用通知栏提醒
if (service.getNotificationManager() == null) {
LogUtils.e(TAG, "handleRemindMessage: 通知管理工具类未初始化,无法发送提醒");
return;
}
NotificationMessage remindMsg = new NotificationMessage();
if (REMIND_TYPE_CHARGE.equals(remindType)) {
remindMsg.setTitle(CHARGE_REMIND_TITLE);
remindMsg.setRemindMSG("charge_remind");
} else {
remindMsg.setTitle(USAGE_REMIND_TITLE);
remindMsg.setRemindMSG("usage_remind");
}
remindMsg.setContent(content);
LogUtils.d(TAG, "handleRemindMessage: 调用通知工具类发送提醒 | remindMSG=" + remindMsg.getRemindMSG());
service.getNotificationManager().showRemindNotification(service, remindMsg);
}
LogUtils.d(TAG, "handleRemindMessage: 提醒流程执行完毕");
}
}
@@ -18,6 +18,8 @@ import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.receivers.ControlCenterServiceReceiver;
import cc.winboll.studio.powerbell.threads.RemindThread;
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
import cc.winboll.studio.powerbell.utils.ChargeMailNotifyHelper;
import cc.winboll.studio.powerbell.utils.ChargeMailNotifyFloatWindow;
import java.util.List;
/**
@@ -93,9 +95,10 @@ public class ControlCenterService extends Service {
LogUtils.d(TAG, "onDestroy() 执行:服务销毁流程启动");
super.onDestroy();
// 资源释放顺序:前台服务 → 线程 → 广播接收器 → Handler → 通知 → 引用(避免内存泄漏)
// 资源释放顺序:前台服务 → 线程 → 悬浮窗 → 广播接收器 → Handler → 通知 → 引用(避免内存泄漏)
stopForegroundService();
RemindThread.stopRemindThread();
ChargeMailNotifyFloatWindow.dismissWindow();
releaseBroadcastReceiver();
destroyHandler();
releaseNotificationResource();
@@ -220,6 +223,19 @@ public class ControlCenterService extends Service {
}
// ====================== 业务组件初始化与销毁(Handler/广播/线程等) ======================
/**
* 检查充电邮件通知功能,启用时显示悬浮窗提示
*/
private void checkChargeMailNotifyAndShowFloatWindow() {
LogUtils.d(TAG, "checkChargeMailNotifyAndShowFloatWindow() 执行");
if (ChargeMailNotifyHelper.isEnabled(this)) {
LogUtils.d(TAG, "checkChargeMailNotifyAndShowFloatWindow() | 充电邮件通知功能已启用,显示悬浮窗");
ChargeMailNotifyFloatWindow.getInstance(this).show();
} else {
LogUtils.d(TAG, "checkChargeMailNotifyAndShowFloatWindow() | 充电邮件通知功能未启用,跳过悬浮窗");
}
}
/**
* 初始化Handler等核心业务组件
*/
@@ -240,6 +256,8 @@ public class ControlCenterService extends Service {
} else {
LogUtils.d(TAG, "initServiceBusinessLogic() | 广播接收器已存在");
}
// 检查充电邮件通知功能,启用时显示悬浮窗提示
checkChargeMailNotifyAndShowFloatWindow();
}
/**
@@ -0,0 +1,115 @@
package cc.winboll.studio.powerbell.threads;
import android.content.Context;
import cc.winboll.studio.libaes.utils.APPMSGMailUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
/**
* APPMSG 邮件通知提醒线程(单例 + 继承 Thread)
* 功能:定时每30秒调用 APPMSGMailUtils 发送电量状态邮件给预设收件人
* 适配:Java7 | API30
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/17
*/
public class APPMSGMailRemindThread extends Thread {
public static final String TAG = "APPMSGMailRemindThread";
// 邮件发送间隔(毫秒)
private static final long MAIL_SEND_INTERVAL = 10000;
// 单例实例
private static APPMSGMailRemindThread sInstance;
// 运行标志
private volatile boolean mIsRunning = false;
private volatile int mBattery;
private volatile boolean mIsCharging;
private Context mContext;
/**
* 私有构造,单例禁用外部 new
*/
private APPMSGMailRemindThread(Context context) {
this.mContext = context.getApplicationContext();
}
/**
* 获取单例(DCL 双重校验)
*/
public static APPMSGMailRemindThread getInstance(Context context) {
if (sInstance == null) {
synchronized (APPMSGMailRemindThread.class) {
if (sInstance == null) {
sInstance = new APPMSGMailRemindThread(context);
}
}
}
return sInstance;
}
/**
* 对外静态启动入口
*/
public static void start(Context context, int battery, boolean isCharging) {
APPMSGMailRemindThread instance = getInstance(context);
instance.mBattery = battery;
instance.mIsCharging = isCharging;
if (!instance.mIsRunning) {
LogUtils.d(TAG, "start() 邮件提醒线程启动");
instance.mIsRunning = true;
instance.start();
}
}
/**
* 对外静态停止入口
*/
public static void stopMail() {
if (sInstance != null) {
LogUtils.d(TAG, "stopMail() 邮件提醒线程停止");
sInstance.mIsRunning = false;
sInstance = null;
}
}
/**
* 线程主逻辑:定时发送邮件
*/
@Override
public void run() {
super.run();
LogUtils.d(TAG, "run() 邮件线程已开始循环");
while (mIsRunning) {
try {
String chargingText = mIsCharging ? "充电" : "用电";
String subject = "PowerBell 电量状态通知";
String content = String.format(
"当前状态:%s,电量为百分之%d",
chargingText, mBattery);
String recipient = APPMSGMailUtils.getRecipient(mContext);
LogUtils.d(TAG, String.format("run() 发送邮件 | 收件人:%s | 内容:%s", recipient, content));
APPMSGMailUtils.sendMail(mContext, subject, content, recipient);
sleep(MAIL_SEND_INTERVAL);
} catch (InterruptedException e) {
LogUtils.e(TAG, "邮件线程被中断", e);
break;
}
}
mIsRunning = false;
LogUtils.d(TAG, "run() 邮件线程已退出");
}
/**
* 是否正在运行
*/
public boolean isRunning() {
return mIsRunning;
}
}
@@ -6,6 +6,7 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.handlers.ControlCenterServiceHandler;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import cc.winboll.studio.powerbell.utils.ChargeMailNotifyHelper;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -189,8 +190,14 @@ public class RemindThread extends Thread {
break;
}
// 启动电量通知 TTS 语音提醒线程
TTSRemindThread.start(this.mContext, App.sQuantityOfElectricity, isCharging);
// 根据 ChargeMailNotify 功能状态选择提醒方式
if (ChargeMailNotifyHelper.isEnabled(this.mContext)) {
// 启用电邮件提醒,不使用 TTS
APPMSGMailRemindThread.start(this.mContext, App.sQuantityOfElectricity, isCharging);
} else {
// 未启用邮件提醒,使用 TTS 语音提醒
TTSRemindThread.start(this.mContext, App.sQuantityOfElectricity, isCharging);
}
// 安全休眠,保留中断标记
safeSleepInternal(sleepTime);
@@ -283,7 +290,12 @@ public class RemindThread extends Thread {
*/
private void cleanThreadStateInternal() {
LogUtils.d(TAG, String.format("cleanThreadStateInternal() 调用 | threadId=%d", getId()));
TTSRemindThread.stopTTS();
// 根据 ChargeMailNotify 功能状态停止对应的提醒线程
if (ChargeMailNotifyHelper.isEnabled(this.mContext)) {
APPMSGMailRemindThread.stopMail();
} else {
TTSRemindThread.stopTTS();
}
isReminding = false;
isExist = true;
// 中断当前线程(如果存活)
@@ -0,0 +1,147 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.MainActivity;
/**
* 充电邮件通知悬浮窗工具类(单例)
* 功能:在服务启动时显示充电邮件通知已启用的悬浮提示
* 适配:Java7 | API30 | 悬浮窗权限检查
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026年07月17日
*/
public class ChargeMailNotifyFloatWindow {
// ====================== 常量区 ======================
public static final String TAG = "ChargeMailNotifyFloatWindow";
// ====================== 单例实例 ======================
private static volatile ChargeMailNotifyFloatWindow sInstance;
// ====================== 成员变量 ======================
private Context mContext;
private WindowManager mWindowManager;
private View mView;
// ====================== 私有构造器(单例模式) ======================
private ChargeMailNotifyFloatWindow(Context context) {
LogUtils.d(TAG, "ChargeMailNotifyFloatWindow() 构造器调用");
this.mContext = context.getApplicationContext();
this.mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
LogUtils.d(TAG, "ChargeMailNotifyFloatWindow() 构造完成");
}
// ====================== 单例获取入口(DCL双重检查锁) ======================
public static ChargeMailNotifyFloatWindow getInstance(Context context) {
if (sInstance == null) {
synchronized (ChargeMailNotifyFloatWindow.class) {
if (sInstance == null) {
sInstance = new ChargeMailNotifyFloatWindow(context);
}
}
}
return sInstance;
}
// ====================== 对外公开方法 ======================
/**
* 显示充电邮件通知悬浮窗
*/
public void show() {
LogUtils.d(TAG, "show() 调用");
// 检查悬浮窗权限
if (!checkOverlayPermission()) {
LogUtils.w(TAG, "show() 悬浮窗权限未授予,跳过显示");
return;
}
// 清理已存在的悬浮窗
dismiss();
try {
// 创建Window布局参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
params.alpha = 0.9f;
params.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
params.x = 0;
params.y = 100;
params.format = PixelFormat.RGBA_8888;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// 加载悬浮窗布局
mView = View.inflate(mContext, R.layout.view_charge_mail_notify, null);
// 点击悬浮窗打开主窗口
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "onClick: 悬浮窗被点击,打开主窗口");
Intent intent = new Intent(mContext, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
mWindowManager.addView(mView, params);
LogUtils.d(TAG, "show() 悬浮窗显示成功");
} catch (Exception e) {
LogUtils.e(TAG, "show() 悬浮窗显示异常", e);
mView = null;
}
}
/**
* 关闭悬浮窗(实例方法)
*/
public void dismiss() {
LogUtils.d(TAG, "dismiss() 调用");
if (mWindowManager != null && mView != null) {
try {
mWindowManager.removeView(mView);
LogUtils.d(TAG, "dismiss() 悬浮窗移除成功");
} catch (Exception e) {
LogUtils.e(TAG, "dismiss() 悬浮窗移除异常", e);
} finally {
mView = null;
}
}
}
/**
* 关闭悬浮窗(静态公共方法,供外部调用)
*/
public static void dismissWindow() {
LogUtils.d(TAG, "dismissWindow() 静态方法调用");
if (sInstance != null) {
sInstance.dismiss();
}
}
// ====================== 私有辅助方法 ======================
/**
* 检查悬浮窗权限
*/
private boolean checkOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean hasPermission = android.provider.Settings.canDrawOverlays(mContext);
LogUtils.d(TAG, "checkOverlayPermission() 权限校验结果=" + hasPermission);
return hasPermission;
}
return true;
}
}
@@ -0,0 +1,44 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.content.SharedPreferences;
import cc.winboll.studio.libappbase.LogUtils;
/**
* 充电邮件通知功能开关管理类
* 适配 API30,基于 Java7 开发
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026年07月17日
* @Describe 管理充电邮件通知功能的启用/关闭状态,使用SP持久化存储
*/
public class ChargeMailNotifyHelper {
// ======================== 静态常量区 =========================
private static final String TAG = "ChargeMailNotifyHelper";
private static final String SP_NAME = "charge_mail_notify_config";
private static final String SP_KEY_ENABLED = "charge_mail_notify_enabled";
private static final boolean DEFAULT_ENABLED = false;
// ======================== 静态方法区 =========================
/**
* 获取充电邮件通知功能是否启用
* @param context 上下文
* @return true=已启用,false=已关闭
*/
public static boolean isEnabled(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
boolean enabled = sp.getBoolean(SP_KEY_ENABLED, DEFAULT_ENABLED);
LogUtils.d(TAG, "isEnabled: " + enabled);
return enabled;
}
/**
* 设置充电邮件通知功能启用/关闭状态
* @param context 上下文
* @param enabled true=启用,false=关闭
*/
public static void setEnabled(Context context, boolean enabled) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
sp.edit().putBoolean(SP_KEY_ENABLED, enabled).apply();
LogUtils.d(TAG, "setEnabled: " + enabled);
}
}
@@ -153,6 +153,51 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
android:background="@drawable/bg_frame"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="5dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="充电邮件通知服务"
android:textSize="16sp"/>
<Switch
android:id="@+id/switchChargeMailNotify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onChargeMailNotifySwitchChanged"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开启后,充电状态变化时将通过邮件发送通知"
android:textSize="12sp"
android:paddingLeft="5dp"
android:paddingBottom="5dp"/>
<cc.winboll.studio.libaes.views.SMTPConfigView
android:id="@+id/smtp_config_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_frame_white"
android:gravity="center_vertical"
android:padding="10dp"
android:id="@+id/viewChargeMailNotifyLinearLayout1">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_email"
android:layout_marginRight="8dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="#FF000000"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="充电邮件通知服务已启用"
android:textColor="#FF000000"
android:textSize="14sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TTS语音播放功能已禁用"
android:textColor="#FF666666"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="电量提醒通知栏功能已禁用"
android:textColor="#FF666666"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>