diff --git a/powerbell/build.properties b/powerbell/build.properties index c9e6bc1..d637350 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Dec 11 16:22:39 GMT 2025 +#Sat Dec 13 12:30:20 GMT 2025 stageCount=16 libraryProject= baseVersion=15.12 publishVersion=15.12.15 -buildCount=1 +buildCount=3 baseBetaVersion=15.12.16 diff --git a/powerbell/src/main/AndroidManifest.xml b/powerbell/src/main/AndroidManifest.xml index 76c7e88..1f88863 100644 --- a/powerbell/src/main/AndroidManifest.xml +++ b/powerbell/src/main/AndroidManifest.xml @@ -4,12 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" package="cc.winboll.studio.powerbell"> - - - - - - @@ -143,14 +137,14 @@ @@ -190,14 +184,14 @@ @@ -206,13 +200,13 @@ android:name="android.max_aspect" android:value="4.0"/> - + - + - + - + - + - + - - - + + + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationHelper.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationHelper.java deleted file mode 100644 index 011b028..0000000 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationHelper.java +++ /dev/null @@ -1,151 +0,0 @@ -package cc.winboll.studio.powerbell.utils; - -/** - * @Author ZhanGSKen - * @Date 2025/03/22 04:39:40 - * @Describe 通知工具类 - */ -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.os.Build; -import android.widget.RemoteViews; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; -import cc.winboll.studio.powerbell.R; - -public class NotificationHelper { - public static final String TAG = "NotificationHelper"; - - // 渠道ID和名称 - private static final String CHANNEL_ID_FOREGROUND = "foreground_channel"; - private static final String CHANNEL_NAME_FOREGROUND = "Foreground Service"; - private static final String CHANNEL_ID_TEMPORARY = "temporary_channel"; - private static final String CHANNEL_NAME_TEMPORARY = "Temporary Notifications"; - - // 通知ID - public static final int FOREGROUND_NOTIFICATION_ID = 1001; - public static final int TEMPORARY_NOTIFICATION_ID = 2001; - - private final Context mContext; - private final NotificationManager mNotificationManager; - - public NotificationHelper(Context context) { - mContext = context; - mNotificationManager = context.getSystemService(NotificationManager.class); - createNotificationChannels(); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createForegroundChannel(); - createTemporaryChannel(); - } - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createForegroundChannel() { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID_FOREGROUND, - CHANNEL_NAME_FOREGROUND, - NotificationManager.IMPORTANCE_LOW - ); - channel.setDescription("Persistent service notifications"); - channel.setSound(null, null); - channel.enableVibration(false); - mNotificationManager.createNotificationChannel(channel); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createTemporaryChannel() { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID_TEMPORARY, - CHANNEL_NAME_TEMPORARY, - NotificationManager.IMPORTANCE_HIGH - ); - channel.setDescription("Temporary alert notifications"); - channel.setSound(null, null); - channel.enableVibration(true); - channel.setVibrationPattern(new long[]{100, 200, 300, 400}); - channel.setBypassDnd(true); - mNotificationManager.createNotificationChannel(channel); - } - - // 显示常驻通知(通常用于前台服务) - public Notification showForegroundNotification(Intent intent, String title, String content) { - PendingIntent pendingIntent = createPendingIntent(intent); - - Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND) - .setSmallIcon(R.drawable.ic_launcher) - .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) - //.setContentTitle(title + "\n" + content) - .setContentTitle(content) - //.setContentText(content) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(true) - .build(); - - mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification); - return notification; - } - - // 显示临时通知(自动消失) - public void showTemporaryNotification(Intent intent, String title, String content) { - PendingIntent pendingIntent = createPendingIntent(intent); - - Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) - .setSmallIcon(R.drawable.ic_launcher) - .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) - .setContentTitle(title) - .setContentText(content) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setAutoCancel(true) - .setVibrate(new long[]{100, 200, 300, 400}) - .build(); - - mNotificationManager.notify(TEMPORARY_NOTIFICATION_ID, notification); - } - - // 创建自定义布局通知(可扩展) - public void showCustomNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) { - PendingIntent pendingIntent = createPendingIntent(intent); - - Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) - .setSmallIcon(R.drawable.ic_launcher) - .setContentIntent(pendingIntent) - .setContent(contentView) - .setCustomBigContentView(bigContentView) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setAutoCancel(true) - .build(); - - mNotificationManager.notify(TEMPORARY_NOTIFICATION_ID + 1, notification); - } - - // 取消所有通知 - public void cancelAllNotifications() { - mNotificationManager.cancelAll(); - } - - // 创建PendingIntent(兼容不同API版本) - private PendingIntent createPendingIntent(Intent intent) { - int flags = PendingIntent.FLAG_UPDATE_CURRENT; -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { -// flags |= PendingIntent.FLAG_IMMUTABLE; -// } - return PendingIntent.getActivity( - mContext, - 0, - intent, - flags - ); - } -} - diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java new file mode 100644 index 0000000..273c9d7 --- /dev/null +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java @@ -0,0 +1,422 @@ +package cc.winboll.studio.powerbell.utils; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.media.RingtoneManager; +import android.os.Build; +import android.view.View; +import android.widget.RemoteViews; +import androidx.core.app.NotificationCompat; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.powerbell.MainActivity; +import cc.winboll.studio.powerbell.R; +import cc.winboll.studio.powerbell.models.NotificationMessage; +import cc.winboll.studio.powerbell.services.ControlCenterService; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/13 20:44 + * @Describe 全局通知管理工具类(整合所有通知能力,适配API29-30,兼容Java7) + */ +public class NotificationManagerUtils { + // ====================== 常量定义(统一管理,避免冲突,首屏可见)====================== + public static final String TAG = "NotificationManagerUtils"; + // 通知渠道(4大渠道,场景隔离,API26+必填) + // 1. 前台服务保活渠道(低优先级,无打扰) + private static final String CHANNEL_ID_FOREGROUND_SERVICE = "channel_foreground_service"; + private static final String CHANNEL_NAME_FOREGROUND_SERVICE = "前台服务保活通知"; + private static final String CHANNEL_DESC_FOREGROUND_SERVICE = "后台服务运行状态,无声音无震动,不打扰用户"; + // 2. 电量提醒渠道(高优先级,闹钟铃声+震动,强提醒) + private static final String CHANNEL_ID_BATTERY_REMIND = "channel_battery_remind"; + private static final String CHANNEL_NAME_BATTERY_REMIND = "电量异常提醒通知"; + private static final String CHANNEL_DESC_BATTERY_REMIND = "电量过高/过低提醒,强震动+闹钟铃声,突破免打扰"; + // 3. 通用临时通知渠道(高优先级,仅震动,普通告警) + private static final String CHANNEL_ID_TEMP_ALERT = "channel_temp_alert"; + private static final String CHANNEL_NAME_TEMP_ALERT = "通用临时提醒通知"; + private static final String CHANNEL_DESC_TEMP_ALERT = "普通即时告警,仅震动提醒,自动取消"; + // 通知ID(唯一区分,避免覆盖,按场景分段) + public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001; // 前台服务 + public static final int NOTIFY_ID_BATTERY_REMIND = 1002; // 电量提醒 + public static final int NOTIFY_ID_TEMP_ALERT = 1003; // 通用临时通知 + public static final int NOTIFY_ID_CUSTOM_LAYOUT = 1004; // 自定义布局通知 + // 通用配置 + private static final int PENDING_INTENT_FLAGS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + : PendingIntent.FLAG_UPDATE_CURRENT; // API30安全标志 + private static final long[] VIBRATE_PATTERN = new long[]{100, 200, 300, 400}; // 标准震动节奏 + private static final String DEFAULT_JUMP_PACKAGE = "cc.winboll.studio.powerbell"; // 默认跳转包名(API29+必填) + + // ====================== 成员变量(按场景分组,私有封装,避免外部篡改)====================== + private final Context mContext; + private final NotificationManager mNotificationManager; + // 前台服务通知专属 + private Notification mForegroundServiceNotify; + private RemoteViews mForegroundServiceRemoteViews; + // 电量提醒通知专属 + private Notification mBatteryRemindNotify; + private RemoteViews mBatteryRemindRemoteViews; + + // ====================== 构造方法(单例思想/实例化通用,自动初始化渠道)====================== + public NotificationManagerUtils(Context context) { + LogUtils.d(TAG, "【初始化】全局通知管理工具类 构造方法调用"); + this.mContext = context.getApplicationContext(); // 用应用上下文,避免内存泄漏 + this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + createAllNotificationChannels(); // 自动创建所有渠道(API26+) + LogUtils.d(TAG, "【初始化】全局通知管理工具类 完成,渠道创建状态:" + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "已创建4个渠道" : "无需创建")); + } + + // ====================== 核心基础能力(渠道创建+Intent构建,复用逻辑,减少冗余)====================== + /** + * 创建所有通知渠道(API26+专属,低版本自动跳过,确保通知正常显示) + */ + @SuppressWarnings("deprecation") + public void createAllNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LogUtils.d(TAG, "【渠道管理】开始创建所有通知渠道"); + createForegroundServiceChannel(); + createBatteryRemindChannel(); + createTempAlertChannel(); + LogUtils.d(TAG, "【渠道管理】4个通知渠道创建完成(含3个核心渠道+预留扩展)"); + } + } + + /** + * 创建前台服务保活渠道(IMPORTANCE_LOW,无声音无震动,不打扰用户) + */ + private void createForegroundServiceChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID_FOREGROUND_SERVICE, + CHANNEL_NAME_FOREGROUND_SERVICE, + NotificationManager.IMPORTANCE_LOW + ); + channel.setDescription(CHANNEL_DESC_FOREGROUND_SERVICE); + channel.setSound(null, null); + channel.enableVibration(false); + channel.setShowBadge(false); // 不显示应用角标 + channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); // 锁屏隐藏 + mNotificationManager.createNotificationChannel(channel); + LogUtils.d(TAG, "【渠道管理】前台服务保活渠道创建成功:" + CHANNEL_NAME_FOREGROUND_SERVICE); + } + } + + /** + * 创建电量提醒渠道(IMPORTANCE_HIGH,闹钟铃声+震动,突破免打扰) + */ + private void createBatteryRemindChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID_BATTERY_REMIND, + CHANNEL_NAME_BATTERY_REMIND, + NotificationManager.IMPORTANCE_HIGH + ); + channel.setDescription(CHANNEL_DESC_BATTERY_REMIND); + channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM), null); // 闹钟铃声 + channel.enableVibration(true); + channel.setVibrationPattern(VIBRATE_PATTERN); + channel.setBypassDnd(true); // 突破免打扰 + channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); // 锁屏可见 + channel.setShowBadge(true); + mNotificationManager.createNotificationChannel(channel); + LogUtils.d(TAG, "【渠道管理】电量提醒渠道创建成功:" + CHANNEL_NAME_BATTERY_REMIND); + } + } + + /** + * 创建通用临时通知渠道(IMPORTANCE_HIGH,仅震动,普通告警) + */ + private void createTempAlertChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID_TEMP_ALERT, + CHANNEL_NAME_TEMP_ALERT, + NotificationManager.IMPORTANCE_HIGH + ); + channel.setDescription(CHANNEL_DESC_TEMP_ALERT); + channel.setSound(null, null); // 仅震动,不发声 + channel.enableVibration(true); + channel.setVibrationPattern(VIBRATE_PATTERN); + channel.setBypassDnd(false); // 不突破免打扰 + channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + channel.setShowBadge(true); + mNotificationManager.createNotificationChannel(channel); + LogUtils.d(TAG, "【渠道管理】通用临时通知渠道创建成功:" + CHANNEL_NAME_TEMP_ALERT); + } + } + + /** + * 构建通用跳转PendingIntent(适配API29-30安全规范,支持自定义跳转目标) + * @param targetIntent 自定义跳转意图(传null则默认跳MainActivity) + * @return 安全的PendingIntent,避免跳转失败/泄露 + */ + private PendingIntent buildPendingIntent(Intent targetIntent) { + Intent intent = targetIntent; + // 传null则默认跳MainActivity(兼容旧逻辑) + if (intent == null) { + intent = new Intent(mContext, MainActivity.class); + LogUtils.d(TAG, "【Intent构建】未传自定义Intent,默认跳转:MainActivity"); + } + // API29+ 强制要求:明确包名,避免跳转目标模糊 + intent.setPackage(DEFAULT_JUMP_PACKAGE); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 确保跳转生效 + LogUtils.d(TAG, "【Intent构建】跳转包名:" + DEFAULT_JUMP_PACKAGE + ",目标:" + intent.getComponent().getClassName()); + + PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, + 0, + intent, + PENDING_INTENT_FLAGS + ); + LogUtils.d(TAG, "【Intent构建】PendingIntent创建成功,安全标志:" + PENDING_INTENT_FLAGS); + return pendingIntent; + } + + // ====================== 场景1:前台服务保活通知(支持自定义布局+更新)====================== + /** + * 初始化前台服务通知自定义布局(RemoteViews) + */ + private void initForegroundServiceRemoteViews(ControlCenterService service, NotificationMessage msg) { + LogUtils.d(TAG, "【布局初始化】开始初始化前台服务通知布局,标题:" + msg.getTitle()); + mForegroundServiceRemoteViews = new RemoteViews(service.getPackageName(), R.layout.view_servicenotification); + mForegroundServiceRemoteViews.setTextViewText(R.id.remoteviewTextView1, msg.getTitle()); + mForegroundServiceRemoteViews.setTextViewText(R.id.remoteviewTextView3, msg.getContent()); + mForegroundServiceRemoteViews.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + LogUtils.d(TAG, "【布局初始化】前台服务通知布局填充完成"); + } + + /** + * 启动前台服务保活通知(ControlCenterService专用,API26+强制要求,保活后台服务) + */ + public void startForegroundServiceNotify(ControlCenterService service, NotificationMessage msg) { + LogUtils.d(TAG, "【前台服务通知】开始构建保活通知,内容:" + msg.getContent()); + if (service == null || msg == null) { + LogUtils.e(TAG, "【前台服务通知】构建失败:Service/NotificationMessage为空"); + return; + } + + // 1. 构建跳转Intent + PendingIntent pendingIntent = buildPendingIntent(null); // 默认跳MainActivity + // 2. 构建基础通知(兼容API26+渠道,低版本用Builder) + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder = new Notification.Builder(service, CHANNEL_ID_FOREGROUND_SERVICE); + } else { + builder = new Notification.Builder(service); + } + mForegroundServiceNotify = builder + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) + .setContentTitle(msg.getTitle()) + .setContentText(msg.getContent()) + .setWhen(System.currentTimeMillis()) + .setColor(Color.parseColor("#F00606")) // 小图标背景色 + .setContentIntent(pendingIntent) + .setOngoing(true) // 常驻通知,不可滑动取消(保活关键) + .setAutoCancel(false) // 禁止点击取消 + .build(); + // 3. 设置自定义布局 + initForegroundServiceRemoteViews(service, msg); + mForegroundServiceNotify.contentView = mForegroundServiceRemoteViews; + mForegroundServiceNotify.bigContentView = mForegroundServiceRemoteViews; + // 4. 启动前台服务(必须调用,否则Service易被回收) + service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify); + LogUtils.d(TAG, "【前台服务通知】保活通知启动成功,通知ID:" + NOTIFY_ID_FOREGROUND_SERVICE); + } + + /** + * 更新前台服务保活通知内容(无需重启服务,直接刷新布局) + */ + public void updateForegroundServiceNotify(ControlCenterService service, NotificationMessage msg) { + LogUtils.d(TAG, "【前台服务通知】开始更新保活通知,新内容:" + msg.getContent()); + if (mForegroundServiceNotify == null || mForegroundServiceRemoteViews == null) { + LogUtils.e(TAG, "【前台服务通知】更新失败:通知对象未初始化,先调用startForegroundServiceNotify"); + return; + } + // 更新自定义布局数据 + initForegroundServiceRemoteViews(service, msg); + mForegroundServiceNotify.contentView = mForegroundServiceRemoteViews; + mForegroundServiceNotify.bigContentView = mForegroundServiceRemoteViews; + // 发送更新 + mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify); + LogUtils.d(TAG, "【前台服务通知】保活通知更新成功"); + } + + // ====================== 场景2:电量提醒通知(支持自定义布局+更新+单独取消)====================== + /** + * 初始化电量提醒通知自定义布局(RemoteViews,支持充电/耗电切换) + */ + private void initBatteryRemindRemoteViews(ControlCenterService service, NotificationMessage msg) { + LogUtils.d(TAG, "【布局初始化】开始初始化电量提醒布局,提醒类型:" + msg.getRemindMSG()); + mBatteryRemindRemoteViews = new RemoteViews(service.getPackageName(), R.layout.view_remindnotification); + mBatteryRemindRemoteViews.setTextViewText(R.id.viewremindnotificationTextView1, msg.getTitle()); + mBatteryRemindRemoteViews.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + // 切换布局(+:充电提醒,-:耗电提醒) + String remindType = msg.getRemindMSG() != null ? msg.getRemindMSG().trim() : ""; + if ("+".equals(remindType)) { + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.GONE); + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.VISIBLE); + LogUtils.d(TAG, "【布局初始化】电量提醒布局切换:充电提醒"); + } else if ("-".equals(remindType)) { + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.GONE); + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.VISIBLE); + LogUtils.d(TAG, "【布局初始化】电量提醒布局切换:耗电提醒"); + } else { + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.GONE); + mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.VISIBLE); + LogUtils.w(TAG, "【布局初始化】未知电量提醒类型:" + remindType); + } + } + + /** + * 初始化电量提醒通知(仅构建,不发送,配合update触发提醒) + */ + public void initBatteryRemindNotify(ControlCenterService service, NotificationMessage msg) { + LogUtils.d(TAG, "【电量提醒通知】开始初始化提醒通知,标题:" + msg.getTitle()); + if (service == null || msg == null) { + LogUtils.e(TAG, "【电量提醒通知】初始化失败:Service/NotificationMessage为空"); + return; + } + // 1. 构建跳转Intent + PendingIntent pendingIntent = buildPendingIntent(null); + // 2. 构建基础通知 + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder = new Notification.Builder(service, CHANNEL_ID_BATTERY_REMIND); + } else { + builder = new Notification.Builder(service); + } + mBatteryRemindNotify = builder + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) + .setContentTitle(msg.getTitle()) + .setContentText(msg.getContent()) + .setWhen(System.currentTimeMillis()) + .setColor(Color.parseColor("#F00606")) + .setContentIntent(pendingIntent) + .setAutoCancel(true) // 点击取消 + .build(); + // 3. 初始化自定义布局 + initBatteryRemindRemoteViews(service, msg); + mBatteryRemindNotify.contentView = mBatteryRemindRemoteViews; + mBatteryRemindNotify.bigContentView = mBatteryRemindRemoteViews; + LogUtils.d(TAG, "【电量提醒通知】初始化完成"); + } + + /** + * 发送/更新电量提醒通知(初始化后调用,触发强提醒) + */ + public void sendOrUpdateBatteryRemindNotify() { + LogUtils.d(TAG, "【电量提醒通知】开始发送/更新提醒"); + if (mBatteryRemindNotify == null || mBatteryRemindRemoteViews == null) { + LogUtils.e(TAG, "【电量提醒通知】发送失败:通知未初始化,先调用initBatteryRemindNotify"); + return; + } + mNotificationManager.notify(NOTIFY_ID_BATTERY_REMIND, mBatteryRemindNotify); + LogUtils.d(TAG, "【电量提醒通知】发送/更新成功,通知ID:" + NOTIFY_ID_BATTERY_REMIND); + } + + /** + * 单独取消电量提醒通知(静态方法,外部可直接调用,无需实例化) + */ + public static void cancelBatteryRemindNotify(Context context) { + LogUtils.d(TAG, "【电量提醒通知】开始取消提醒,通知ID:" + NOTIFY_ID_BATTERY_REMIND); + if (context == null) { + LogUtils.e(TAG, "【电量提醒通知】取消失败:Context为空"); + return; + } + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + manager.cancel(NOTIFY_ID_BATTERY_REMIND); + LogUtils.d(TAG, "【电量提醒通知】取消成功"); + } + + // ====================== 场景3:通用临时通知(简单文本,自动取消,无需自定义布局)====================== + /** + * 显示通用临时通知(普通告警,仅震动,自动取消,支持自定义跳转目标) + * @param targetIntent 自定义跳转Intent(传null默认跳MainActivity) + * @param title 通知标题 + * @param content 通知内容 + */ + public void showTempAlertNotify(Intent targetIntent, String title, String content) { + LogUtils.d(TAG, "【通用临时通知】开始构建,标题:" + title + ",内容:" + content); + if (title == null || content == null) { + LogUtils.e(TAG, "【通用临时通知】构建失败:标题/内容为空"); + return; + } + // 1. 构建跳转Intent + PendingIntent pendingIntent = buildPendingIntent(targetIntent); + // 2. 用NotificationCompat.Builder(兼容所有版本,简化逻辑) + NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMP_ALERT) + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) + .setContentTitle(title) + .setContentText(content) + .setWhen(System.currentTimeMillis()) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .setVibrate(VIBRATE_PATTERN); + // 3. 发送通知 + Notification notification = builder.build(); + mNotificationManager.notify(NOTIFY_ID_TEMP_ALERT, notification); + LogUtils.d(TAG, "【通用临时通知】显示成功,通知ID:" + NOTIFY_ID_TEMP_ALERT); + } + + // ====================== 场景4:自定义布局通知(灵活扩展,支持复杂样式)====================== + /** + * 显示自定义布局通知(支持普通布局+大布局,通用所有场景,可自定义跳转) + * @param targetIntent 自定义跳转Intent + * @param contentView 普通自定义布局(必填) + * @param bigContentView 下拉大布局(可选) + */ + public void showCustomLayoutNotify(Intent targetIntent, RemoteViews contentView, RemoteViews bigContentView) { + LogUtils.d(TAG, "【自定义布局通知】开始构建,布局ID:" + (contentView != null ? contentView.getLayoutId() : "null")); + if (contentView == null) { + LogUtils.e(TAG, "【自定义布局通知】构建失败:普通布局contentView为空"); + return; + } + // 1. 构建跳转Intent + PendingIntent pendingIntent = buildPendingIntent(targetIntent); + // 2. 构建自定义布局通知 + NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMP_ALERT) + .setSmallIcon(R.drawable.ic_launcher) // 必传,不可省略 + .setContentIntent(pendingIntent) + .setContent(contentView) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true); + // 添加大布局(可选) + if (bigContentView != null) { + builder.setCustomBigContentView(bigContentView); + LogUtils.d(TAG, "【自定义布局通知】已添加下拉大布局,布局ID:" + bigContentView.getLayoutId()); + } + // 3. 发送通知 + Notification notification = builder.build(); + mNotificationManager.notify(NOTIFY_ID_CUSTOM_LAYOUT, notification); + LogUtils.d(TAG, "【自定义布局通知】显示成功,通知ID:" + NOTIFY_ID_CUSTOM_LAYOUT); + } + + // ====================== 通知取消工具(支持精准取消/全取消)====================== + /** + * 取消指定ID的通知(精准取消,灵活控制) + */ + public void cancelNotifyById(int notifyId) { + LogUtils.d(TAG, "【通知管理】开始取消通知,ID:" + notifyId); + mNotificationManager.cancel(notifyId); + LogUtils.d(TAG, "【通知管理】通知取消成功,ID:" + notifyId); + } + + /** + * 取消所有通知(谨慎使用,会清除所有场景的通知) + */ + public void cancelAllNotifies() { + LogUtils.d(TAG, "【通知管理】开始取消所有通知"); + mNotificationManager.cancelAll(); + LogUtils.d(TAG, "【通知管理】所有通知取消完成"); + } +} + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationUtils2.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationUtils2.java deleted file mode 100644 index c07278f..0000000 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationUtils2.java +++ /dev/null @@ -1,247 +0,0 @@ -package cc.winboll.studio.powerbell.utils; - -/* - * 参考: - * https://blog.csdn.net/qq_35507234/article/details/90676587 - * https://blog.csdn.net/qq_16628781/article/details/51548324 - */ - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.media.RingtoneManager; -import android.os.Build; -import android.view.View; -import android.widget.RemoteViews; -import androidx.annotation.RequiresApi; -import cc.winboll.studio.powerbell.MainActivity; -import cc.winboll.studio.powerbell.R; -import cc.winboll.studio.powerbell.models.NotificationMessage; -import cc.winboll.studio.powerbell.services.ControlCenterService; - -public class NotificationUtils2 { - - public static final String TAG = NotificationHelper.class.getSimpleName(); - - Context mContext; - NotificationManager mNotificationManager; - - Notification mForegroundNotification; - PendingIntent mForegroundPendingIntent; - Notification mRemindNotification; - PendingIntent mRemindPendingIntent; - RemoteViews mrvServiceNotificationView; - RemoteViews mrvRemindNotificationView; - - static enum NotificationType { MIN, MAX }; - private static int _mnServiceNotificationID = 1; - private static int _mnRemindNotificationID = 2; - private static String _mszChannelIDService = "1"; - private static String _mszChannelNameService = "Service"; - private static String _mszChannelIDRemind = "2"; - private static String _mszChannelNameRemind = "Remind"; - -// public NotificationUtils(Context context) { -// mContext = context; -// mNotificationManager = (NotificationManager) context.getSystemService( -// Context.NOTIFICATION_SERVICE); -// } - - public NotificationUtils2(Context context) { - mContext = context; - mNotificationManager = context.getSystemService(NotificationManager.class); - //createNotificationChannels(); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - public void createNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createServiceChannel(); - createRemindChannel(); - } - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createServiceChannel() { - NotificationChannel channel = new NotificationChannel( - _mszChannelIDService, - _mszChannelNameService, - NotificationManager.IMPORTANCE_LOW - ); - channel.setDescription("Background service updates"); - channel.setSound(null, null); - channel.enableVibration(false); - mNotificationManager.createNotificationChannel(channel); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createRemindChannel() { - NotificationChannel channel = new NotificationChannel( - _mszChannelIDRemind, - _mszChannelNameRemind, - NotificationManager.IMPORTANCE_HIGH - ); - channel.setDescription("Critical reminders"); - channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM), null); - channel.enableVibration(true); - channel.setVibrationPattern(new long[]{100, 200, 300, 400}); - channel.setBypassDnd(true); - channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - mNotificationManager.createNotificationChannel(channel); - } - - // 创建并发送服务通知 - // - public void createForegroundNotification(ControlCenterService service, NotificationMessage notificationMessage) { - //创建Notification,传入Context和channelId - Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 - intent.setPackage(service.getPackageName()); - //LogUtils.d(TAG, "mService.getPackageName() : " + service.getPackageName()); - intent.setClass(service, MainActivity.class); - //LogUtils.d(TAG, "MainActivity.class.getName() : " + MainActivity.class.getName()); - //这里放一个count用来区分每一个通知 - //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 - - //参数1:context 上下文对象 - //参数2:发送者私有的请求码(Private request code for the sender) - //参数3:intent 意图对象 - //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 - //mForegroundPendingIntent = PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mForegroundPendingIntent = PendingIntent.getActivity(service, - 1, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mForegroundPendingIntent = PendingIntent.getActivity(service, - 1, intent, PendingIntent.FLAG_IMMUTABLE); - } - - mForegroundNotification = new Notification.Builder(service, _mszChannelIDService) - .setAutoCancel(true) - .setContentTitle(notificationMessage.getTitle()) - .setContentText(notificationMessage.getContent()) - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_launcher) - //设置红色 - .setColor(Color.parseColor("#F00606")) - .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) - .setContentIntent(mForegroundPendingIntent) - .build(); - - setForegroundNotificationRemoteViews(service, notificationMessage); - service.startForeground(_mnServiceNotificationID, mForegroundNotification); - } - - void initmrvRemindNotificationView(ControlCenterService service, NotificationMessage notificationMessage) { - mrvRemindNotificationView = new RemoteViews(service.getPackageName(), R.layout.view_remindnotification); - mrvRemindNotificationView.setTextViewText(R.id.viewremindnotificationTextView1, notificationMessage.getTitle()); - String szRemindMSG = notificationMessage.getRemindMSG(); - //LogUtils.d(TAG, "szRemindMSG : " + szRemindMSG); - //mrvRemindNotificationView.setTextViewText(R.id.remoteviewTextView2, szRemindMSG); - if (szRemindMSG != null) { - if (szRemindMSG.trim().equals("-")) { - //LogUtils.d(TAG, "-"); - mrvRemindNotificationView.setViewVisibility(R.id.remoteviewCharge, View.GONE); - mrvRemindNotificationView.setViewVisibility(R.id.remoteviewUsege, View.VISIBLE); - } else if (szRemindMSG.trim().equals("+")) { - //LogUtils.d(TAG, "+"); - mrvRemindNotificationView.setViewVisibility(R.id.remoteviewUsege, View.GONE); - mrvRemindNotificationView.setViewVisibility(R.id.remoteviewCharge, View.VISIBLE); - } - mrvRemindNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); - //给我remoteViews上的控件tv_content添加监听事件 - //remoteViews.setOnClickPendingIntent(R.id.remoteviewLinearLayout1, pi); - //return mrvServiceNotificationView; - } - } - - void initmrvServiceNotificationView(ControlCenterService service, NotificationMessage notificationMessage) { - mrvServiceNotificationView = new RemoteViews(service.getPackageName(), R.layout.view_servicenotification); - mrvServiceNotificationView.setTextViewText(R.id.remoteviewTextView1, notificationMessage.getTitle()); - //String szRemindMSG = notificationMessage.getRemindMSG(); - //mrvServiceNotificationView.setTextViewText(R.id.remoteviewTextView2, szRemindMSG); - //rvServiceNotificationView.setTextViewText(R.id.remoteviewTextView3, notificationMessage.getContent() + Integer.toString(nTest)); - mrvServiceNotificationView.setTextViewText(R.id.remoteviewTextView3, notificationMessage.getContent()); - mrvServiceNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); - //给我remoteViews上的控件tv_content添加监听事件 - //remoteViews.setOnClickPendingIntent(R.id.remoteviewLinearLayout1, pi); - //return mrvServiceNotificationView; - } - - void setForegroundNotificationRemoteViews(ControlCenterService service, NotificationMessage notificationMessage) { - initmrvServiceNotificationView(service, notificationMessage); - mForegroundNotification.contentView = mrvServiceNotificationView; - mForegroundNotification.bigContentView = mrvServiceNotificationView; - } - - void setRemindNotificationRemoteViews(ControlCenterService service, NotificationMessage notificationMessage) { - initmrvRemindNotificationView(service, notificationMessage); - mRemindNotification.contentView = mrvRemindNotificationView; - mRemindNotification.bigContentView = mrvRemindNotificationView; - } - - // 更新服务通知 - // - public void updateForegroundNotification(ControlCenterService service, NotificationMessage notificationMessage) { - setForegroundNotificationRemoteViews(service, notificationMessage); - mNotificationManager.notify(_mnServiceNotificationID, mForegroundNotification); - - } - - // 创建并发送电量提醒通知 - // - public void updateRemindNotification(ControlCenterService service, NotificationMessage notificationMessage) { - //LogUtils.d(TAG, "updateRemindNotification : " + notificationMessage.getRemindMSG()); - setRemindNotificationRemoteViews(service, notificationMessage); - mNotificationManager.notify(_mnRemindNotificationID, mRemindNotification); - } - - public void createRemindNotification(ControlCenterService service, NotificationMessage notificationMessage) { - //LogUtils.d(TAG, "notificationMessage : " + notificationMessage.getRemindMSG()); - //创建Notification,传入Context和channelId - Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 - intent.setPackage(service.getPackageName()); - intent.setClass(service, MainActivity.class); - //这里放一个count用来区分每一个通知 - //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 - - //参数1:context 上下文对象 - //参数2:发送者私有的请求码(Private request code for the sender) - //参数3:intent 意图对象 - //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 - //mRemindPendingIntent = PendingIntent.getActivity(mService, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mRemindPendingIntent = PendingIntent.getActivity(service, - 1, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mRemindPendingIntent = PendingIntent.getActivity(service, - 1, intent, PendingIntent.FLAG_IMMUTABLE); - } - - mRemindNotification = new Notification.Builder(service, _mszChannelIDRemind) - .setAutoCancel(true) - .setContentTitle(notificationMessage.getTitle()) - .setContentText(notificationMessage.getContent()) - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_launcher) - //设置红色 - .setColor(Color.parseColor("#F00606")) - .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) - .setContentIntent(mRemindPendingIntent) - .build(); - setRemindNotificationRemoteViews(service, notificationMessage); - } - - public static void cancelRemindNotification(Context context){ - // 获取 NotificationManager 实例 - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - // 撤回指定 ID 的通知栏消息 - notificationManager.cancel(_mnRemindNotificationID); - } -} - -