Loading src/com/android/deskclock/AlarmInitReceiver.java +35 −22 Original line number Diff line number Diff line Loading @@ -33,8 +33,16 @@ public class AlarmInitReceiver extends BroadcastReceiver { private static final String PREF_VOLUME_DEF_DONE = "vol_def_done"; /** * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on * TIME_SET, TIMEZONE_CHANGED * This receiver handles a variety of actions: * * <ul> * <li>Clean up backup data that was recently restored to this device on * ACTION_COMPLETE_RESTORE.</li> * <li>Clean up backup data that was recently restored to this device and reset timers and * clear stopwatch on ACTION_BOOT_COMPLETED</li> * <li>Fix alarm states on ACTION_BOOT_COMPLETED, TIME_SET, TIMEZONE_CHANGED, * and LOCALE_CHANGED</li> * </ul> */ @Override public void onReceive(final Context context, Intent intent) { Loading @@ -45,14 +53,15 @@ public class AlarmInitReceiver extends BroadcastReceiver { final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context); wl.acquire(); // We need to increment the global id out of the async task to prevent // race conditions // We need to increment the global id out of the async task to prevent race conditions AlarmStateManager.updateGlobalIntentId(context); AsyncHandler.post(new Runnable() { @Override public void run() { if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { try { if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { // Clear stopwatch and timers data SharedPreferences prefs = final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); LogUtils.v("AlarmInitReceiver - Reset timers and clear stopwatch data"); TimerObj.resetTimersInSharedPrefs(prefs); Loading @@ -65,12 +74,16 @@ public class AlarmInitReceiver extends BroadcastReceiver { } } // Process restored data if any exists if (!DeskClockBackupAgent.processRestoredData(context)) { // Update all the alarm instances on time change event AlarmStateManager.fixAlarmInstances(context); } } finally { result.finish(); LogUtils.v("AlarmInitReceiver finished"); wl.release(); LogUtils.v("AlarmInitReceiver finished"); } } }); } Loading src/com/android/deskclock/DeskClockBackupAgent.java +80 −5 Original line number Diff line number Diff line Loading @@ -16,10 +16,19 @@ package com.android.deskclock; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import com.android.deskclock.alarms.AlarmStateManager; Loading @@ -33,6 +42,11 @@ import java.util.List; public class DeskClockBackupAgent extends BackupAgent { private static final String KEY_RESTORE_FINISHED = "restore_finished"; public static final String ACTION_COMPLETE_RESTORE = "com.android.deskclock.action.COMPLETE_RESTORE"; private static final String TAG = "DeskClockBackupAgent"; @Override Loading @@ -56,28 +70,89 @@ public class DeskClockBackupAgent extends BackupAgent { super.onRestoreFile(data, size, destination, type, mode, mtime); } /** * When this method is called during backup/restore, the application is executing in a * "minimalist" state. Because of this, the application's ContentResolver cannot be used. * Consequently, the work of scheduling alarms on the restore device cannot be done here. * Instead, a future callback to DeskClock is used as a signal to reschedule the alarms. The * future callback may take the form of ACTION_BOOT_COMPLETED if the device is not yet fully * booted (i.e. the restore occurred as part of the setup wizard). If the device is booted, an * ACTION_COMPLETE_RESTORE broadcast is scheduled 10 seconds in the future to give * backup/restore enough time to kill the Clock process. Both of these future callbacks result * in the execution of {@link #processRestoredData(Context)}. */ @Override public void onRestoreFinished() { // Now that alarms have been restored, schedule them in AlarmManager. final List<Alarm> alarms = Alarm.getAlarms(getContentResolver(), null); // Write a preference to indicate a data restore has been completed. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs.edit().putBoolean(KEY_RESTORE_FINISHED, true).apply(); // If device boot is not yet completed, use ACTION_BOOT_COMPLETED to trigger completion of // the data restore process at a safer time. if (registerReceiver(null, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)) != null) { LogUtils.i(TAG, "Waiting for %s to complete the data restore", Intent.ACTION_BOOT_COMPLETED); return; } // Otherwise, the device is already booted, so schedule a custom broadcast to start the // application in 10 seconds. // Create an Intent to send into DeskClock indicating restore is complete. final PendingIntent restoreIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_COMPLETE_RESTORE).setClass(this, AlarmInitReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); // Deliver the Intent 10 seconds from now. final long triggerAtMillis = SystemClock.elapsedRealtime() + 10000; // Schedule the Intent delivery in AlarmManager. final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, restoreIntent); LogUtils.i(TAG, "Waiting for %s to complete the data restore", ACTION_COMPLETE_RESTORE); } /** * @param context a context to access resources and services * @return {@code true} if restore data was processed; {@code false} otherwise. */ public static boolean processRestoredData(Context context) { // If the preference indicates data was not recently restored, there is nothing to do. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (!prefs.getBoolean(KEY_RESTORE_FINISHED, false)) { return false; } LogUtils.i(TAG, "processRestoredData() started"); // Now that alarms have been restored, schedule new instances in AlarmManager. final ContentResolver contentResolver = context.getContentResolver(); final List<Alarm> alarms = Alarm.getAlarms(contentResolver, null); final Calendar now = Calendar.getInstance(); for (Alarm alarm : alarms) { // Remove any instances that may currently exist for the alarm; // these aren't relevant on the restore device and we'll recreate them below. AlarmStateManager.deleteAllInstances(this, alarm.id); AlarmStateManager.deleteAllInstances(context, alarm.id); if (alarm.enabled) { // Create the next alarm instance to schedule. AlarmInstance alarmInstance = alarm.createInstanceAfter(now); // Add the next alarm instance to the database. alarmInstance = AlarmInstance.addInstance(getContentResolver(), alarmInstance); alarmInstance = AlarmInstance.addInstance(contentResolver, alarmInstance); // Schedule the next alarm instance in AlarmManager. AlarmStateManager.registerInstance(this, alarmInstance, true); AlarmStateManager.registerInstance(context, alarmInstance, true); LogUtils.i(TAG, "DeskClockBackupAgent scheduled alarm instance: %s", alarmInstance); } } // Remove the preference to avoid executing this logic multiple times. prefs.edit().remove(KEY_RESTORE_FINISHED).apply(); LogUtils.i(TAG, "processRestoredData() completed"); return true; } } No newline at end of file Loading
src/com/android/deskclock/AlarmInitReceiver.java +35 −22 Original line number Diff line number Diff line Loading @@ -33,8 +33,16 @@ public class AlarmInitReceiver extends BroadcastReceiver { private static final String PREF_VOLUME_DEF_DONE = "vol_def_done"; /** * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on * TIME_SET, TIMEZONE_CHANGED * This receiver handles a variety of actions: * * <ul> * <li>Clean up backup data that was recently restored to this device on * ACTION_COMPLETE_RESTORE.</li> * <li>Clean up backup data that was recently restored to this device and reset timers and * clear stopwatch on ACTION_BOOT_COMPLETED</li> * <li>Fix alarm states on ACTION_BOOT_COMPLETED, TIME_SET, TIMEZONE_CHANGED, * and LOCALE_CHANGED</li> * </ul> */ @Override public void onReceive(final Context context, Intent intent) { Loading @@ -45,14 +53,15 @@ public class AlarmInitReceiver extends BroadcastReceiver { final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context); wl.acquire(); // We need to increment the global id out of the async task to prevent // race conditions // We need to increment the global id out of the async task to prevent race conditions AlarmStateManager.updateGlobalIntentId(context); AsyncHandler.post(new Runnable() { @Override public void run() { if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { try { if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { // Clear stopwatch and timers data SharedPreferences prefs = final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); LogUtils.v("AlarmInitReceiver - Reset timers and clear stopwatch data"); TimerObj.resetTimersInSharedPrefs(prefs); Loading @@ -65,12 +74,16 @@ public class AlarmInitReceiver extends BroadcastReceiver { } } // Process restored data if any exists if (!DeskClockBackupAgent.processRestoredData(context)) { // Update all the alarm instances on time change event AlarmStateManager.fixAlarmInstances(context); } } finally { result.finish(); LogUtils.v("AlarmInitReceiver finished"); wl.release(); LogUtils.v("AlarmInitReceiver finished"); } } }); } Loading
src/com/android/deskclock/DeskClockBackupAgent.java +80 −5 Original line number Diff line number Diff line Loading @@ -16,10 +16,19 @@ package com.android.deskclock; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import com.android.deskclock.alarms.AlarmStateManager; Loading @@ -33,6 +42,11 @@ import java.util.List; public class DeskClockBackupAgent extends BackupAgent { private static final String KEY_RESTORE_FINISHED = "restore_finished"; public static final String ACTION_COMPLETE_RESTORE = "com.android.deskclock.action.COMPLETE_RESTORE"; private static final String TAG = "DeskClockBackupAgent"; @Override Loading @@ -56,28 +70,89 @@ public class DeskClockBackupAgent extends BackupAgent { super.onRestoreFile(data, size, destination, type, mode, mtime); } /** * When this method is called during backup/restore, the application is executing in a * "minimalist" state. Because of this, the application's ContentResolver cannot be used. * Consequently, the work of scheduling alarms on the restore device cannot be done here. * Instead, a future callback to DeskClock is used as a signal to reschedule the alarms. The * future callback may take the form of ACTION_BOOT_COMPLETED if the device is not yet fully * booted (i.e. the restore occurred as part of the setup wizard). If the device is booted, an * ACTION_COMPLETE_RESTORE broadcast is scheduled 10 seconds in the future to give * backup/restore enough time to kill the Clock process. Both of these future callbacks result * in the execution of {@link #processRestoredData(Context)}. */ @Override public void onRestoreFinished() { // Now that alarms have been restored, schedule them in AlarmManager. final List<Alarm> alarms = Alarm.getAlarms(getContentResolver(), null); // Write a preference to indicate a data restore has been completed. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs.edit().putBoolean(KEY_RESTORE_FINISHED, true).apply(); // If device boot is not yet completed, use ACTION_BOOT_COMPLETED to trigger completion of // the data restore process at a safer time. if (registerReceiver(null, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)) != null) { LogUtils.i(TAG, "Waiting for %s to complete the data restore", Intent.ACTION_BOOT_COMPLETED); return; } // Otherwise, the device is already booted, so schedule a custom broadcast to start the // application in 10 seconds. // Create an Intent to send into DeskClock indicating restore is complete. final PendingIntent restoreIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_COMPLETE_RESTORE).setClass(this, AlarmInitReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); // Deliver the Intent 10 seconds from now. final long triggerAtMillis = SystemClock.elapsedRealtime() + 10000; // Schedule the Intent delivery in AlarmManager. final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, restoreIntent); LogUtils.i(TAG, "Waiting for %s to complete the data restore", ACTION_COMPLETE_RESTORE); } /** * @param context a context to access resources and services * @return {@code true} if restore data was processed; {@code false} otherwise. */ public static boolean processRestoredData(Context context) { // If the preference indicates data was not recently restored, there is nothing to do. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (!prefs.getBoolean(KEY_RESTORE_FINISHED, false)) { return false; } LogUtils.i(TAG, "processRestoredData() started"); // Now that alarms have been restored, schedule new instances in AlarmManager. final ContentResolver contentResolver = context.getContentResolver(); final List<Alarm> alarms = Alarm.getAlarms(contentResolver, null); final Calendar now = Calendar.getInstance(); for (Alarm alarm : alarms) { // Remove any instances that may currently exist for the alarm; // these aren't relevant on the restore device and we'll recreate them below. AlarmStateManager.deleteAllInstances(this, alarm.id); AlarmStateManager.deleteAllInstances(context, alarm.id); if (alarm.enabled) { // Create the next alarm instance to schedule. AlarmInstance alarmInstance = alarm.createInstanceAfter(now); // Add the next alarm instance to the database. alarmInstance = AlarmInstance.addInstance(getContentResolver(), alarmInstance); alarmInstance = AlarmInstance.addInstance(contentResolver, alarmInstance); // Schedule the next alarm instance in AlarmManager. AlarmStateManager.registerInstance(this, alarmInstance, true); AlarmStateManager.registerInstance(context, alarmInstance, true); LogUtils.i(TAG, "DeskClockBackupAgent scheduled alarm instance: %s", alarmInstance); } } // Remove the preference to avoid executing this logic multiple times. prefs.edit().remove(KEY_RESTORE_FINISHED).apply(); LogUtils.i(TAG, "processRestoredData() completed"); return true; } } No newline at end of file