|
|
/*
|
|
|
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
|
|
*
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
* You may obtain a copy of the License at
|
|
|
*
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
*
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
* See the License for the specific language governing permissions and
|
|
|
* limitations under the License.
|
|
|
*/
|
|
|
|
|
|
package net.micode.notes.ui;
|
|
|
|
|
|
import android.app.Activity;
|
|
|
import android.app.AlertDialog;
|
|
|
import android.content.Context;
|
|
|
import android.content.DialogInterface;
|
|
|
import android.content.Intent;
|
|
|
import android.media.AudioManager;
|
|
|
import android.media.MediaPlayer;
|
|
|
import android.media.RingtoneManager;
|
|
|
import android.net.Uri;
|
|
|
import android.os.Bundle;
|
|
|
import android.os.Handler;
|
|
|
import android.os.Looper;
|
|
|
import android.os.Vibrator;
|
|
|
import android.util.Log;
|
|
|
import android.view.Window;
|
|
|
import android.view.WindowManager;
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
import net.micode.notes.R;
|
|
|
import net.micode.notes.tool.DataUtils;
|
|
|
|
|
|
public class NewAlarmAlertActivity extends Activity {
|
|
|
private static final String TAG = "NewAlarmAlertActivity";
|
|
|
private long mNoteId;
|
|
|
private String mSnippet;
|
|
|
private MediaPlayer mPlayer;
|
|
|
private static final int SNIPPET_PREW_MAX_LEN = 60;
|
|
|
|
|
|
@Override
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
super.onCreate(savedInstanceState);
|
|
|
Log.d(TAG, "onCreate called");
|
|
|
|
|
|
// 设置一个简单的透明布局,确保Activity能够正常创建
|
|
|
setContentView(R.layout.alarm_alert);
|
|
|
|
|
|
// 直接初始化并显示对话框
|
|
|
|
|
|
// 检查启动来源,避免在不应该显示的时候显示
|
|
|
if (!isValidLaunch()) {
|
|
|
Log.d(TAG, "Invalid launch, finishing activity");
|
|
|
finish();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 设置窗口属性,确保在任何情况下都能显示
|
|
|
setupWindow();
|
|
|
|
|
|
// 处理Intent数据
|
|
|
processIntent();
|
|
|
|
|
|
// 显示提醒对话框
|
|
|
showAlertDialog();
|
|
|
|
|
|
// 播放提醒声音
|
|
|
playAlarmSound();
|
|
|
|
|
|
// 触发震动提醒
|
|
|
vibrate();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 检查启动来源是否有效
|
|
|
*/
|
|
|
private boolean isValidLaunch() {
|
|
|
Intent intent = getIntent();
|
|
|
if (intent == null) {
|
|
|
Log.e(TAG, "Intent is null");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 检查是否是系统广播,如果是则不启动提醒窗口
|
|
|
String action = intent.getAction();
|
|
|
if (action != null) {
|
|
|
Log.d(TAG, "Received action: " + action);
|
|
|
// 跳过所有系统广播触发的提醒
|
|
|
if (action.equals(Intent.ACTION_BOOT_COMPLETED) ||
|
|
|
action.equals(Intent.ACTION_TIME_CHANGED) ||
|
|
|
action.equals(Intent.ACTION_TIMEZONE_CHANGED) ||
|
|
|
action.equals(Intent.ACTION_REBOOT) ||
|
|
|
action.equals(Intent.ACTION_SHUTDOWN) ||
|
|
|
action.equals(Intent.ACTION_USER_PRESENT) ||
|
|
|
action.equals(Intent.ACTION_SCREEN_ON) ||
|
|
|
action.equals(Intent.ACTION_SCREEN_OFF)) {
|
|
|
Log.d(TAG, "Skipping alert for system action: " + action);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 简化检查:只要不是系统广播,就认为是有效的启动
|
|
|
// 这样可以确保任何来自AlarmManager或AlarmReceiver的启动都能通过
|
|
|
Log.d(TAG, "Valid launch: non-system broadcast");
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
private void setupWindow() {
|
|
|
// 无标题栏
|
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
|
|
|
|
// 获取窗口
|
|
|
final Window win = getWindow();
|
|
|
|
|
|
// 设置窗口标志,确保在锁屏状态下也能显示
|
|
|
win.addFlags(
|
|
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
|
|
|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
|
|
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
|
|
|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
|
|
);
|
|
|
|
|
|
// 设置窗口属性
|
|
|
WindowManager.LayoutParams params = win.getAttributes();
|
|
|
params.width = WindowManager.LayoutParams.MATCH_PARENT;
|
|
|
params.height = WindowManager.LayoutParams.MATCH_PARENT;
|
|
|
params.alpha = 1.0f;
|
|
|
params.dimAmount = 0.7f;
|
|
|
win.setAttributes(params);
|
|
|
}
|
|
|
|
|
|
private void processIntent() {
|
|
|
Intent intent = getIntent();
|
|
|
Log.d(TAG, "Processing intent: " + intent);
|
|
|
|
|
|
// 尝试获取noteId
|
|
|
if (intent != null) {
|
|
|
// 从data中获取
|
|
|
if (intent.getData() != null) {
|
|
|
try {
|
|
|
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
|
|
|
Log.d(TAG, "Note ID from data: " + mNoteId);
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error parsing note ID from data", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 从extras中获取
|
|
|
if (mNoteId == 0 && intent.getExtras() != null) {
|
|
|
mNoteId = intent.getLongExtra("note_id", 0);
|
|
|
if (mNoteId == 0) {
|
|
|
mNoteId = intent.getLongExtra("noteId", 0);
|
|
|
}
|
|
|
if (mNoteId == 0) {
|
|
|
mNoteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
|
|
|
}
|
|
|
Log.d(TAG, "Note ID from extras: " + mNoteId);
|
|
|
}
|
|
|
|
|
|
// 获取笔记内容
|
|
|
if (mNoteId > 0) {
|
|
|
try {
|
|
|
mSnippet = DataUtils.getSnippetById(getContentResolver(), mNoteId);
|
|
|
if (mSnippet == null || mSnippet.isEmpty()) {
|
|
|
mSnippet = "提醒时间到";
|
|
|
} else if (mSnippet.length() > SNIPPET_PREW_MAX_LEN) {
|
|
|
mSnippet = mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + "...";
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error getting note snippet", e);
|
|
|
mSnippet = "提醒时间到";
|
|
|
}
|
|
|
} else {
|
|
|
mSnippet = "提醒时间到";
|
|
|
}
|
|
|
} else {
|
|
|
mSnippet = "提醒时间到";
|
|
|
}
|
|
|
|
|
|
Log.d(TAG, "Final noteId: " + mNoteId + ", snippet: " + mSnippet);
|
|
|
}
|
|
|
|
|
|
private void showAlertDialog() {
|
|
|
Log.d(TAG, "Showing alert dialog");
|
|
|
|
|
|
// 直接在UI线程显示对话框,不使用Handler,确保立即显示
|
|
|
try {
|
|
|
// 使用现代主题,确保对话框在手机顶部弹出
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
|
|
|
builder.setTitle(R.string.app_name);
|
|
|
builder.setMessage(mSnippet);
|
|
|
builder.setCancelable(false);
|
|
|
|
|
|
// 设置按钮
|
|
|
builder.setPositiveButton(R.string.notealert_ok, (dialog, which) -> {
|
|
|
Log.d(TAG, "OK button clicked");
|
|
|
// 停止声音和震动
|
|
|
stopAlarm();
|
|
|
dialog.dismiss();
|
|
|
finish();
|
|
|
});
|
|
|
|
|
|
// 查看笔记按钮
|
|
|
builder.setNegativeButton(R.string.notealert_enter, (dialog, which) -> {
|
|
|
Log.d(TAG, "View note button clicked");
|
|
|
// 停止声音和震动
|
|
|
stopAlarm();
|
|
|
if (mNoteId > 0) {
|
|
|
Intent intent = new Intent(NewAlarmAlertActivity.this, NoteEditActivity.class);
|
|
|
intent.putExtra(Intent.EXTRA_UID, mNoteId);
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
startActivity(intent);
|
|
|
}
|
|
|
dialog.dismiss();
|
|
|
finish();
|
|
|
});
|
|
|
|
|
|
// 添加稍后提醒按钮
|
|
|
builder.setNeutralButton("稍后提醒", (dialog, which) -> {
|
|
|
Log.d(TAG, "Snooze button clicked");
|
|
|
// 停止声音和震动
|
|
|
stopAlarm();
|
|
|
// 设置稍后提醒
|
|
|
scheduleSnooze();
|
|
|
dialog.dismiss();
|
|
|
finish();
|
|
|
});
|
|
|
|
|
|
// 创建并显示对话框
|
|
|
AlertDialog dialog = builder.create();
|
|
|
|
|
|
// 设置对话框属性
|
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
|
|
|
|
// 立即显示对话框,不设置复杂的窗口属性,确保在所有设备上都能显示
|
|
|
dialog.show();
|
|
|
Log.d(TAG, "Alert dialog shown immediately");
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error showing alert dialog", e);
|
|
|
// 如果对话框显示失败,尝试使用系统默认对话框
|
|
|
try {
|
|
|
AlertDialog.Builder simpleBuilder = new AlertDialog.Builder(this);
|
|
|
simpleBuilder.setTitle(R.string.app_name);
|
|
|
simpleBuilder.setMessage("提醒时间到");
|
|
|
simpleBuilder.setCancelable(false);
|
|
|
simpleBuilder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
|
stopAlarm();
|
|
|
dialog.dismiss();
|
|
|
finish();
|
|
|
});
|
|
|
|
|
|
// 添加稍后提醒按钮
|
|
|
simpleBuilder.setNeutralButton("稍后提醒", (dialog, which) -> {
|
|
|
stopAlarm();
|
|
|
scheduleSnooze();
|
|
|
dialog.dismiss();
|
|
|
finish();
|
|
|
});
|
|
|
|
|
|
AlertDialog simpleDialog = simpleBuilder.create();
|
|
|
simpleDialog.show();
|
|
|
Log.d(TAG, "Simple alert dialog shown");
|
|
|
} catch (Exception ex) {
|
|
|
Log.e(TAG, "Error showing simple dialog", ex);
|
|
|
// 即使对话框完全失败,也显示Toast提示
|
|
|
try {
|
|
|
Toast.makeText(this, "提醒时间到", Toast.LENGTH_LONG).show();
|
|
|
Log.d(TAG, "Toast shown as final fallback");
|
|
|
} catch (Exception toastEx) {
|
|
|
Log.e(TAG, "Error showing toast", toastEx);
|
|
|
}
|
|
|
// 停止声音和震动
|
|
|
stopAlarm();
|
|
|
// 3秒后自动退出
|
|
|
new Handler(Looper.getMainLooper()).postDelayed(this::finish, 3000);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void scheduleSnooze() {
|
|
|
Log.d(TAG, "Scheduling snooze for note: " + mNoteId);
|
|
|
|
|
|
// 设置5分钟后再次提醒
|
|
|
long snoozeTime = System.currentTimeMillis() + 5 * 60 * 1000;
|
|
|
|
|
|
try {
|
|
|
// 创建闹钟Intent
|
|
|
Intent intent = new Intent(this, AlarmReceiver.class);
|
|
|
intent.setAction("net.micode.notes.ALARM_TRIGGERED");
|
|
|
if (mNoteId > 0) {
|
|
|
intent.setData(android.content.ContentUris.withAppendedId(net.micode.notes.data.Notes.CONTENT_NOTE_URI, mNoteId));
|
|
|
intent.putExtra(Intent.EXTRA_UID, mNoteId);
|
|
|
}
|
|
|
intent.putExtra("from_alarm_manager", true);
|
|
|
|
|
|
// 创建PendingIntent
|
|
|
int flags = android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
|
|
flags |= android.app.PendingIntent.FLAG_IMMUTABLE;
|
|
|
}
|
|
|
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(
|
|
|
this,
|
|
|
(int) mNoteId,
|
|
|
intent,
|
|
|
flags
|
|
|
);
|
|
|
|
|
|
// 设置闹钟
|
|
|
android.app.AlarmManager alarmManager = (android.app.AlarmManager) getSystemService(ALARM_SERVICE);
|
|
|
if (alarmManager != null) {
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
|
|
alarmManager.setExactAndAllowWhileIdle(android.app.AlarmManager.RTC_WAKEUP, snoozeTime, pendingIntent);
|
|
|
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
|
|
alarmManager.setExact(android.app.AlarmManager.RTC_WAKEUP, snoozeTime, pendingIntent);
|
|
|
} else {
|
|
|
alarmManager.set(android.app.AlarmManager.RTC_WAKEUP, snoozeTime, pendingIntent);
|
|
|
}
|
|
|
Log.d(TAG, "Snooze alarm scheduled for: " + snoozeTime);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error scheduling snooze", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void playAlarmSound() {
|
|
|
Log.d(TAG, "Playing alarm sound");
|
|
|
|
|
|
try {
|
|
|
// 获取默认闹钟铃声
|
|
|
Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
|
|
|
|
|
|
// 如果没有设置闹钟铃声,使用通知铃声
|
|
|
if (alarmUri == null) {
|
|
|
alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
|
|
}
|
|
|
|
|
|
// 如果仍然没有,使用默认铃声
|
|
|
if (alarmUri == null) {
|
|
|
alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
|
|
|
}
|
|
|
|
|
|
if (alarmUri != null) {
|
|
|
Log.d(TAG, "Using ringtone: " + alarmUri);
|
|
|
|
|
|
mPlayer = new MediaPlayer();
|
|
|
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
|
|
|
mPlayer.setDataSource(this, alarmUri);
|
|
|
mPlayer.setLooping(true);
|
|
|
mPlayer.prepare();
|
|
|
mPlayer.start();
|
|
|
Log.d(TAG, "Alarm sound started");
|
|
|
} else {
|
|
|
Log.w(TAG, "No ringtone available");
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error playing alarm sound", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void vibrate() {
|
|
|
Log.d(TAG, "Starting vibration");
|
|
|
|
|
|
try {
|
|
|
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
|
|
|
if (vibrator != null) {
|
|
|
// 检查设备是否支持震动
|
|
|
if (vibrator.hasVibrator()) {
|
|
|
// 定义震动模式:[延迟, 震动时长, 间隔, 震动时长, ...]
|
|
|
long[] pattern = {0, 1000, 500, 1000, 500, 1000};
|
|
|
// 重复模式:-1表示不重复,0表示从索引0开始重复
|
|
|
vibrator.vibrate(pattern, -1);
|
|
|
Log.d(TAG, "Vibration started");
|
|
|
} else {
|
|
|
Log.w(TAG, "Device does not support vibration");
|
|
|
}
|
|
|
} else {
|
|
|
Log.e(TAG, "Vibrator service not available");
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error starting vibration", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void stopAlarm() {
|
|
|
Log.d(TAG, "Stopping alarm");
|
|
|
|
|
|
// 停止播放声音
|
|
|
if (mPlayer != null) {
|
|
|
try {
|
|
|
mPlayer.stop();
|
|
|
mPlayer.release();
|
|
|
mPlayer = null;
|
|
|
Log.d(TAG, "Alarm sound stopped");
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error stopping alarm sound", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 停止震动
|
|
|
try {
|
|
|
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
|
|
|
if (vibrator != null) {
|
|
|
vibrator.cancel();
|
|
|
Log.d(TAG, "Vibration stopped");
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error stopping vibration", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
protected void onDestroy() {
|
|
|
super.onDestroy();
|
|
|
Log.d(TAG, "onDestroy called");
|
|
|
|
|
|
// 停止所有提醒
|
|
|
stopAlarm();
|
|
|
}
|
|
|
|
|
|
// 静态方法,用于启动提醒
|
|
|
public static void start(Context context, long noteId) {
|
|
|
Log.d(TAG, "Starting NewAlarmAlertActivity for note: " + noteId);
|
|
|
|
|
|
Intent intent = new Intent(context, NewAlarmAlertActivity.class);
|
|
|
intent.putExtra("note_id", noteId);
|
|
|
intent.putExtra("from_alarm_manager", true); // 确保添加启动标记
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
|
|
try {
|
|
|
context.startActivity(intent);
|
|
|
Log.d(TAG, "NewAlarmAlertActivity started successfully");
|
|
|
} catch (Exception e) {
|
|
|
Log.e(TAG, "Error starting NewAlarmAlertActivity", e);
|
|
|
// 如果直接启动失败,尝试使用服务
|
|
|
try {
|
|
|
Intent serviceIntent = new Intent(context, NewAlarmAlertService.class);
|
|
|
serviceIntent.putExtra("note_id", noteId);
|
|
|
serviceIntent.putExtra("from_alarm_manager", true); // 确保添加启动标记
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
|
context.startForegroundService(serviceIntent);
|
|
|
} else {
|
|
|
context.startService(serviceIntent);
|
|
|
}
|
|
|
Log.d(TAG, "NewAlarmAlertService started as fallback");
|
|
|
} catch (Exception ex) {
|
|
|
Log.e(TAG, "Error starting NewAlarmAlertService", ex);
|
|
|
// 如果服务也启动失败,尝试显示一个简单的Toast提示
|
|
|
try {
|
|
|
android.widget.Toast.makeText(context, "提醒时间到", android.widget.Toast.LENGTH_LONG).show();
|
|
|
Log.d(TAG, "Toast shown as final fallback");
|
|
|
} catch (Exception toastEx) {
|
|
|
Log.e(TAG, "Error showing toast", toastEx);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|