feat: 增加整点报时,增加闹钟
This commit is contained in:
@@ -6,8 +6,8 @@ import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
@@ -15,44 +15,86 @@ import com.ttstd.dialer.activity.alarm.AlarmAlertActivity;
|
||||
import com.ttstd.dialer.alarmclock.AlarmManagerHelper;
|
||||
import com.ttstd.dialer.alarmclock.AlarmRepeatConfig;
|
||||
import com.ttstd.dialer.alarmclock.AlarmTimeCalculator;
|
||||
import com.ttstd.dialer.db.alarm.AlarmInfo;
|
||||
import com.ttstd.dialer.db.alarm.AlarmRepository;
|
||||
import com.ttstd.dialer.db.alarm.IntegerListConverter;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AlarmReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "AlarmReceiver";
|
||||
private static final String CHANNEL_ID = "alarm_channel";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// 1. 执行原有的响铃和弹出通知/界面的逻辑
|
||||
// (调用上一次回答中创建的通知栏或全屏拉起逻辑)
|
||||
showAlarmNotification(context);
|
||||
int alarmId = intent.getIntExtra("ALARM_ID", -1);
|
||||
Logger.d(TAG, "收到闹钟广播, ID: " + alarmId);
|
||||
|
||||
// 2. 核心:动态轮转,实现重复闹钟
|
||||
scheduleNextRepeatAlarm(context);
|
||||
}
|
||||
|
||||
private void scheduleNextRepeatAlarm(Context context) {
|
||||
// 从 SharedPreferences 中取出用户之前保存的闹钟时间和重复设置
|
||||
// 实际开发中此处可以替换为 SQLite 数据库
|
||||
SharedPreferences sp = context.getSharedPreferences("AlarmPrefs", Context.MODE_PRIVATE);
|
||||
int hour = sp.getInt("alarm_hour", 8);
|
||||
int minute = sp.getInt("alarm_minute", 0);
|
||||
int repeatType = sp.getInt("repeat_type", AlarmRepeatConfig.REPEAT_ONCE);
|
||||
|
||||
// 如果是“只响一次”,响完就结束了,不需要再设置
|
||||
if (repeatType == AlarmRepeatConfig.REPEAT_ONCE) {
|
||||
if (alarmId == -1) {
|
||||
Logger.e(TAG, "收到闹钟广播但 ALARM_ID 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是 每天 或 周一至周五
|
||||
AlarmRepeatConfig config = new AlarmRepeatConfig(repeatType);
|
||||
// 使用同步方法获取闹钟信息
|
||||
AlarmRepository repository = new AlarmRepository(context);
|
||||
AlarmInfo alarmInfo = repository.getAlarmById(alarmId);
|
||||
|
||||
// 重新计算下一次的时间(由于此时已经过了当前闹钟点,计算出的必定是未来的时间)
|
||||
long nextTriggerTime = AlarmTimeCalculator.calculateNextAlarmTime(hour, minute, config);
|
||||
if (alarmInfo == null) {
|
||||
Logger.e(TAG, "未找到对应的闹钟数据, ID: " + alarmId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 再次调用第一步写好的精准闹钟设置工具,埋下新的定时炸弹
|
||||
AlarmManagerHelper.setExactAlarm(context, nextTriggerTime);
|
||||
if (!alarmInfo.isEnabled()) {
|
||||
Logger.w(TAG, "闹钟已被禁用,不再处理: " + alarmId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 执行响铃和弹出通知逻辑
|
||||
showAlarmNotification(context, alarmInfo);
|
||||
|
||||
// 尝试直接启动 Activity
|
||||
try {
|
||||
Intent alertIntent = new Intent(context, AlarmAlertActivity.class);
|
||||
alertIntent.putExtra("AlarmInfo", (Parcelable) alarmInfo);
|
||||
alertIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
context.startActivity(alertIntent);
|
||||
} catch (Exception e) {
|
||||
Logger.e(TAG, "直接启动 AlarmAlertActivity 失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 2. 核心:动态轮转,实现重复闹钟
|
||||
scheduleNextRepeatAlarm(context, repository, alarmInfo);
|
||||
}
|
||||
|
||||
private void showAlarmNotification(Context context) {
|
||||
private void scheduleNextRepeatAlarm(Context context, AlarmRepository repository, AlarmInfo alarmInfo) {
|
||||
// 如果是“只响一次”,响完就将其置为禁用状态
|
||||
if (alarmInfo.getRepeatType() == AlarmRepeatConfig.REPEAT_ONCE) {
|
||||
alarmInfo.setEnabled(false);
|
||||
repository.updateAlarm(alarmInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新计算下一次的时间
|
||||
AlarmRepeatConfig config = new AlarmRepeatConfig(alarmInfo.getRepeatType());
|
||||
if (alarmInfo.getRepeatType() == AlarmRepeatConfig.REPEAT_CUSTOM) {
|
||||
List<Integer> days = IntegerListConverter.toIntegerList(alarmInfo.getCustomDays());
|
||||
config.setCustomDays(new ArrayList<>(days));
|
||||
}
|
||||
|
||||
long nextTriggerTime = AlarmTimeCalculator.calculateNextAlarmTime(
|
||||
alarmInfo.getHour(), alarmInfo.getMinute(), config);
|
||||
|
||||
// 更新数据库中的下次触发时间
|
||||
alarmInfo.setNextTriggerTime(nextTriggerTime);
|
||||
repository.updateAlarm(alarmInfo);
|
||||
|
||||
// 再次调用精准闹钟设置工具,埋下新的定时炸弹
|
||||
AlarmManagerHelper.setExactAlarm(context, alarmInfo.getId(), nextTriggerTime);
|
||||
}
|
||||
|
||||
private void showAlarmNotification(Context context, AlarmInfo alarmInfo) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notificationManager == null) return;
|
||||
|
||||
@@ -68,23 +110,27 @@ public class AlarmReceiver extends BroadcastReceiver {
|
||||
|
||||
// 2. 构建点击通知或全屏弹出的 Intent
|
||||
Intent alertIntent = new Intent(context, AlarmAlertActivity.class);
|
||||
alertIntent.putExtra("AlarmInfo", (Parcelable) alarmInfo);
|
||||
alertIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
|
||||
PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, alertIntent, flags);
|
||||
|
||||
// 3. 构建高优先级通知(兼容 Android 10+ 后台全屏拉起)
|
||||
// 使用 alarmId 作为 requestCode 区分不同的闹钟通知
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, alarmInfo.getId(), alertIntent, flags);
|
||||
|
||||
// 3. 构建高优先级通知
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
|
||||
.setContentTitle("闹钟响了")
|
||||
.setContentText("时间到了,快起床!")
|
||||
.setContentText(alarmInfo.getLabel() != null && !alarmInfo.getLabel().isEmpty() ?
|
||||
alarmInfo.getLabel() : "时间到了,快起床!")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setAutoCancel(true)
|
||||
.setFullScreenIntent(pendingIntent, true); // 核心:锁屏时直接拉起 Activity
|
||||
|
||||
notificationManager.notify(1, builder.build());
|
||||
notificationManager.notify(alarmInfo.getId(), builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.ttstd.dialer.receiver;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class HourlyChimeManager {
|
||||
private static final String TAG = "HourlyChimeManager";
|
||||
private static final int REQUEST_CODE = 1001;
|
||||
|
||||
public static void startChime(Context context) {
|
||||
Logger.d(TAG, "启动整点报时");
|
||||
scheduleNextChime(context);
|
||||
}
|
||||
|
||||
public static void stopChime(Context context) {
|
||||
Logger.d(TAG, "停止整点报时");
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
if (alarmManager == null) return;
|
||||
|
||||
Intent intent = new Intent(context, HourlyChimeReceiver.class);
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
|
||||
PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flags);
|
||||
alarmManager.cancel(pendingIntent);
|
||||
}
|
||||
|
||||
public static void scheduleNextChime(Context context) {
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
if (alarmManager == null) return;
|
||||
|
||||
// 检查 Android 12+ 的精准闹钟权限
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (!alarmManager.canScheduleExactAlarms()) {
|
||||
Logger.w(TAG, "没有精准闹钟权限,无法安排报时");
|
||||
// 暂时不主动跳转,避免打扰用户
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
long triggerAtMillis = calendar.getTimeInMillis();
|
||||
|
||||
Intent intent = new Intent(context, HourlyChimeReceiver.class);
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
|
||||
PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flags);
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
|
||||
} else {
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
|
||||
}
|
||||
Logger.d(TAG, "已安排下一次报时: " + calendar.getTime().toString());
|
||||
} catch (SecurityException e) {
|
||||
Logger.e(TAG, "由于安全权限无法设置闹钟: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ttstd.dialer.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.tts.sherpa_onnx.SherpaOnnxTtsManager;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class HourlyChimeReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "HourlyChimeReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Logger.d(TAG, "收到广播: " + intent.getAction());
|
||||
|
||||
MMKV mmkv = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
boolean enabled = mmkv.decodeInt(CommonConfig.HOURLY_CHIME_ENABLE, 0) == 1;
|
||||
|
||||
if (!enabled) {
|
||||
Logger.d(TAG, "整点报时未开启,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Logger.d(TAG, "开机完成,重新调度整点报时");
|
||||
HourlyChimeManager.scheduleNextChime(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
String text = "现在是北京时间 " + hour + " 点整";
|
||||
|
||||
Logger.d(TAG, "开始播报: " + text);
|
||||
SherpaOnnxTtsManager.getInstance().init(context);
|
||||
SherpaOnnxTtsManager.getInstance().speak(text);
|
||||
|
||||
// 安排下一个小时的报时
|
||||
HourlyChimeManager.scheduleNextChime(context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user