From f5d80701256e6b66e36f894cfb8ee2a5c74fd0aa Mon Sep 17 00:00:00 2001 From: Raj Yengisetty Date: Thu, 2 Apr 2015 15:55:58 -0700 Subject: DeskClock: re-write delayAlarm for intercepting alarms when in-call While the previous clean-up was necessary, it no longer meets the spec. This feature needs to trigger the alarm immediately after the call finishes. Change-Id: I71107277926c891f79b1e46fb27878994ac15ce0 (cherry picked from commit b685ff5e8f578101fc52c262d74bbc4f915aa460) --- AndroidManifest.xml | 6 + src/com/android/deskclock/AlarmInitReceiver.java | 2 +- .../deskclock/alarms/AlarmStateManager.java | 150 +++++++++++---------- .../deskclock/alarms/PhoneStateReceiver.java | 69 ++++++++++ 4 files changed, 155 insertions(+), 72 deletions(-) create mode 100644 src/com/android/deskclock/alarms/PhoneStateReceiver.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 77e1d2670..72246f7e0 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -105,6 +105,12 @@ android:exported="false"> + + + + + + diff --git a/src/com/android/deskclock/AlarmInitReceiver.java b/src/com/android/deskclock/AlarmInitReceiver.java index 93d319bcb..26808ea9b 100644 --- a/src/com/android/deskclock/AlarmInitReceiver.java +++ b/src/com/android/deskclock/AlarmInitReceiver.java @@ -49,7 +49,7 @@ public class AlarmInitReceiver extends BroadcastReceiver { // We need to increment the global id out of the async task to prevent // race conditions - AlarmStateManager.updateGloablIntentId(context); + AlarmStateManager.updateGlobalIntentId(context); AsyncHandler.post(new Runnable() { @Override public void run() { // Remove the snooze alarm after a boot. diff --git a/src/com/android/deskclock/alarms/AlarmStateManager.java b/src/com/android/deskclock/alarms/AlarmStateManager.java index 412469e9c..67e0f90d1 100755 --- a/src/com/android/deskclock/alarms/AlarmStateManager.java +++ b/src/com/android/deskclock/alarms/AlarmStateManager.java @@ -44,7 +44,9 @@ import com.android.deskclock.provider.Alarm; import com.android.deskclock.provider.AlarmInstance; import java.util.Calendar; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * This class handles all the state changes for alarm instances. You need to @@ -115,6 +117,9 @@ public final class AlarmStateManager extends BroadcastReceiver { // Extra key to set the global broadcast id. private static final String ALARM_GLOBAL_ID_EXTRA = "intent.extra.alarm.global.id"; + // key to to retrieve pending alarm set + public static final String ALARM_PENDING_ALARM_KEY = "pending.alarm.key"; + // Intent category tag used when schedule state change intents in alarm manager. public static final String ALARM_MANAGER_TAG = "ALARM_MANAGER"; @@ -133,8 +138,17 @@ public final class AlarmStateManager extends BroadcastReceiver { return prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1); } - public static void updateGloablIntentId(Context context) { + public static void updateGlobalIntentId(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + // If there are any pending alarms, do not update the global id - pending alarms + // will be ignored by the receivers when the user tries to dismiss or snooze + Set alarms = prefs.getStringSet(AlarmStateManager.ALARM_PENDING_ALARM_KEY, + new HashSet()); + if (alarms.size() > 0) { + return; + } + int globalId = prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1) + 1; prefs.edit().putInt(ALARM_GLOBAL_ID_EXTRA, globalId).commit(); } @@ -676,7 +690,7 @@ public final class AlarmStateManager extends BroadcastReceiver { * @param instance to change state on * @param state to change to */ - public void setAlarmState(Context context, AlarmInstance instance, int state) { + public static void setAlarmState(Context context, AlarmInstance instance, int state) { switch(state) { case AlarmInstance.SILENT_STATE: setSilentState(context, instance); @@ -733,49 +747,7 @@ public final class AlarmStateManager extends BroadcastReceiver { final String action = intent.getAction(); LogUtils.v("AlarmStateManager received intent " + intent); if (CHANGE_STATE_ACTION.equals(action)) { - Uri uri = intent.getData(); - AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), - AlarmInstance.getId(uri)); - if (instance == null) { - // Not a big deal, but it shouldn't happen - LogUtils.e("Can not change state for unknown instance: " + uri); - return; - } - - int globalId = getGlobalIntentId(context); - int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1); - int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1); - if (intentId != globalId) { - LogUtils.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId + - " AlarmState: " + alarmState); - return; - } - - // If the phone is busy, keep the alarm snoozing.When the call is ended, - // the new coming alarm or the alarm which wakes from sooze,will skip the codes here - // and continue show the alarm as normal. - if (context.getResources().getBoolean(R.bool.config_delayalarm)) { - TelephonyManager mTelephonyManager = (TelephonyManager) context - .getSystemService(Context.TELEPHONY_SERVICE); - if ((mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) - && (alarmState == AlarmInstance.FIRED_STATE)) { - snooze(context, intent, instance); - return; - } - } - - if (alarmState >= 0) { - setAlarmState(context, instance, alarmState); - } else { - // No need to register instance again when alarmState - // equals POWER_OFF_ALARM_STATE. POWER_OFF_ALARM_STATE - // is an invalid state for rtc power off alarm. - if (alarmState == AlarmInstance.POWER_OFF_ALARM_STATE) - { - return; - } - registerInstance(context, instance, true); - } + handleChangeStateIntent(context, intent); } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) { Uri uri = intent.getData(); AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), @@ -795,37 +767,73 @@ public final class AlarmStateManager extends BroadcastReceiver { } } - /** - * Make the alarm snooze based on the snooze interval in settings. - * If the phone is not busy in call anymore, this method will not be - * called, and the alarm will wake up based on snooze interval. - */ - private void snooze(Context context, Intent intent, AlarmInstance instance) { + private static void handleChangeStateIntent(Context context, Intent intent) { Uri uri = intent.getData(); - AlarmInstance newInstance = AlarmInstance.getInstance(context.getContentResolver(), + AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), AlarmInstance.getId(uri)); - if (newInstance == null) { - // If AlarmInstance is turn to null,return. + if (instance == null) { + // Not a big deal, but it shouldn't happen + LogUtils.e("Can not change state for unknown instance: " + uri); + return; + } + + int globalId = getGlobalIntentId(context); + int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1); + int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1); + if (intentId != globalId) { + LogUtils.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId + + " AlarmState: " + alarmState); return; } - // Notify the user that the alarm has been snoozed. - Intent cancelSnooze = createStateChangeIntent(context, ALARM_MANAGER_TAG, newInstance, - AlarmInstance.DISMISSED_STATE); - PendingIntent broadcast = PendingIntent.getBroadcast(context, instance.hashCode(), - cancelSnooze, 0); - String label = newInstance.getLabelOrDefault(context); - label = context.getString(R.string.alarm_notify_snooze_label, label); - NotificationManager nm = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); - Notification n = new Notification(R.drawable.stat_notify_alarm, label, 0); - n.setLatestEventInfo(context, label, - context.getString(R.string.alarm_notify_snooze_text, - AlarmUtils.getFormattedTime(context, instance.getAlarmTime())), - broadcast); - n.flags |= Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONGOING_EVENT; - nm.notify(instance.hashCode(), n); - setAlarmState(context, instance, AlarmInstance.SNOOZE_STATE); + // If the phone is busy, add the alarm to a string set in shared preferenecs that will be + // cleared when the call is ended. + if (context.getResources().getBoolean(R.bool.config_delayalarm)) { + TelephonyManager mTelephonyManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + if ((mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) + && (alarmState == AlarmInstance.FIRED_STATE)) { + pendAlarm(context, uri, alarmState); + return; + } + } + + setChangeAlarmState(context, instance, alarmState); + } + + public static void setChangeAlarmState(Context context, AlarmInstance instance, + int alarmState) { + + if (alarmState >= 0) { + setAlarmState(context, instance, alarmState); + } else { + // No need to register instance again when alarmState + // equals POWER_OFF_ALARM_STATE. POWER_OFF_ALARM_STATE + // is an invalid state for rtc power off alarm. + if (alarmState == AlarmInstance.POWER_OFF_ALARM_STATE) { + return; + } + registerInstance(context, instance, true); + } + } + + /** + * Add the alarm to list of pending Alarms to be fired after the call is complete. + */ + private static void pendAlarm(Context context, Uri uri, int alarmState) { + LogUtils.v("Pending alarm: " + uri); + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + Set alarms = sp.getStringSet(ALARM_PENDING_ALARM_KEY, new HashSet()); + + // Alarms are stored as "|" + String alarm = uri.toString() + "|" + alarmState; + + HashSet newSet = new HashSet(); + newSet.addAll(alarms); + newSet.add(alarm); + + sp.edit().putStringSet(ALARM_PENDING_ALARM_KEY, newSet).commit(); } /** diff --git a/src/com/android/deskclock/alarms/PhoneStateReceiver.java b/src/com/android/deskclock/alarms/PhoneStateReceiver.java new file mode 100644 index 000000000..2ddcfd3a4 --- /dev/null +++ b/src/com/android/deskclock/alarms/PhoneStateReceiver.java @@ -0,0 +1,69 @@ +package com.android.deskclock.alarms; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import com.android.deskclock.LogUtils; +import com.android.deskclock.provider.AlarmInstance; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Watch the phone state to clear any alarms that are waiting for a call to end + */ +public class PhoneStateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + LogUtils.v("PhoneStateReceiver received intent " + intent); + + if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { + TelephonyManager mTelephonyManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + if (mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) { + // New call state is idle, update state for any pending alarms + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + Set alarms = sp.getStringSet(AlarmStateManager.ALARM_PENDING_ALARM_KEY, + new HashSet()); + if (alarms.size() <= 0) { + return; // no alarms to fire + } + + Iterator iterator = alarms.iterator(); + + while (iterator.hasNext()) { + String flatAlarm = iterator.next(); + if (TextUtils.isEmpty(flatAlarm)) { + LogUtils.e("Unable to un-flatten alarm for restore"); + return; + } + + String [] items = flatAlarm.split("\\|"); + if (items.length < 2) { + LogUtils.e("Unable to un-flatten alarm for restore"); + return; + } + + Uri uri = Uri.parse(items[0]); + AlarmInstance instance = AlarmInstance.getInstance( + context.getContentResolver(), AlarmInstance.getId(uri)); + int alarmState = Integer.parseInt(items[1]); + + AlarmStateManager.setChangeAlarmState(context, instance, alarmState); + iterator.remove(); + } + + // Clear out the pending alarms + sp.edit().remove(AlarmStateManager.ALARM_PENDING_ALARM_KEY).commit(); + } + } + } +} \ No newline at end of file -- cgit v1.2.3