1b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux/* 2b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * Copyright (C) 2015 The Android Open Source Project 3b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * 4b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * Licensed under the Apache License, Version 2.0 (the "License"); 5b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * you may not use this file except in compliance with the License. 6b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * You may obtain a copy of the License at 7b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * 8b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * http://www.apache.org/licenses/LICENSE-2.0 9b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * 10b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * Unless required by applicable law or agreed to in writing, software 11b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * distributed under the License is distributed on an "AS IS" BASIS, 12b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * See the License for the specific language governing permissions and 14b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux * limitations under the License. 15b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux */ 16b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 17b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuxpackage com.android.deskclock; 18b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 1932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.app.AlarmManager; 2032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.app.PendingIntent; 21b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.app.backup.BackupAgent; 22b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.app.backup.BackupDataInput; 23b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.app.backup.BackupDataOutput; 2432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.content.ContentResolver; 2532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.content.Context; 2632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.content.Intent; 2732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.content.IntentFilter; 2832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.content.SharedPreferences; 29b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.os.ParcelFileDescriptor; 3032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.os.SystemClock; 3132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.preference.PreferenceManager; 32b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.support.annotation.NonNull; 33b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 34b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport com.android.deskclock.alarms.AlarmStateManager; 351f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport com.android.deskclock.provider.Alarm; 361f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport com.android.deskclock.provider.AlarmInstance; 37b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 38b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport java.io.File; 39b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport java.io.IOException; 401f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport java.util.Calendar; 411f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport java.util.List; 42b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 43b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuxpublic class DeskClockBackupAgent extends BackupAgent { 44b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 4532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux private static final String KEY_RESTORE_FINISHED = "restore_finished"; 4632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 4732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux public static final String ACTION_COMPLETE_RESTORE = 4832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux "com.android.deskclock.action.COMPLETE_RESTORE"; 4932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 501f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux private static final String TAG = "DeskClockBackupAgent"; 511f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 52b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 53b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 54b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux ParcelFileDescriptor newState) throws IOException { } 55b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 56b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 57b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestore(BackupDataInput data, int appVersionCode, 58b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux ParcelFileDescriptor newState) throws IOException { } 59b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 60b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 61b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestoreFile(@NonNull ParcelFileDescriptor data, long size, File destination, 62b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux int type, long mode, long mtime) throws IOException { 63b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux // The preference file on the backup device may not be the same on the restore device. 64b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux // Massage the file name here before writing it. 65b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux if (destination.getName().endsWith("_preferences.xml")) { 66b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux final String prefFileName = getPackageName() + "_preferences.xml"; 67b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux destination = new File(destination.getParentFile(), prefFileName); 68b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 69b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 70b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux super.onRestoreFile(data, size, destination, type, mode, mtime); 71b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 72b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 7332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux /** 7432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * When this method is called during backup/restore, the application is executing in a 7532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * "minimalist" state. Because of this, the application's ContentResolver cannot be used. 7632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * Consequently, the work of scheduling alarms on the restore device cannot be done here. 7732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * Instead, a future callback to DeskClock is used as a signal to reschedule the alarms. The 7832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * future callback may take the form of ACTION_BOOT_COMPLETED if the device is not yet fully 7932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * booted (i.e. the restore occurred as part of the setup wizard). If the device is booted, an 8032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * ACTION_COMPLETE_RESTORE broadcast is scheduled 10 seconds in the future to give 8132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * backup/restore enough time to kill the Clock process. Both of these future callbacks result 8232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * in the execution of {@link #processRestoredData(Context)}. 8332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux */ 84b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 85b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestoreFinished() { 864a025d30d308222b78ed68897da89e2b264a5fb8Jeff Sharkey if (Utils.isNOrLater()) { 874a025d30d308222b78ed68897da89e2b264a5fb8Jeff Sharkey // TODO: migrate restored database and preferences over into 8881b7b5362e4ddc7be2a52e6be27ea732acc7b8f7Jeff Sharkey // the device-protected storage area 894a025d30d308222b78ed68897da89e2b264a5fb8Jeff Sharkey } 904a025d30d308222b78ed68897da89e2b264a5fb8Jeff Sharkey 9132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Write a preference to indicate a data restore has been completed. 92942b3a3956221969c77f9abc447444505eea6929Jeff Sharkey final SharedPreferences prefs = Utils.getDefaultSharedPreferences(this); 9332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux prefs.edit().putBoolean(KEY_RESTORE_FINISHED, true).apply(); 9432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 9532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Create an Intent to send into DeskClock indicating restore is complete. 9632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final PendingIntent restoreIntent = PendingIntent.getBroadcast(this, 0, 9732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux new Intent(ACTION_COMPLETE_RESTORE).setClass(this, AlarmInitReceiver.class), 9832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); 9932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 10032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Deliver the Intent 10 seconds from now. 10132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final long triggerAtMillis = SystemClock.elapsedRealtime() + 10000; 10232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 10332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Schedule the Intent delivery in AlarmManager. 10432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 10532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, restoreIntent); 10632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 10732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux LogUtils.i(TAG, "Waiting for %s to complete the data restore", ACTION_COMPLETE_RESTORE); 10832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux } 10932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 11032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux /** 11132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * @param context a context to access resources and services 11232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * @return {@code true} if restore data was processed; {@code false} otherwise. 11332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux */ 11432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux public static boolean processRestoredData(Context context) { 11532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // If the preference indicates data was not recently restored, there is nothing to do. 116942b3a3956221969c77f9abc447444505eea6929Jeff Sharkey final SharedPreferences prefs = Utils.getDefaultSharedPreferences(context); 11732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux if (!prefs.getBoolean(KEY_RESTORE_FINISHED, false)) { 11832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux return false; 11932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux } 12032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 12132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux LogUtils.i(TAG, "processRestoredData() started"); 12232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 12332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Now that alarms have been restored, schedule new instances in AlarmManager. 12432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final ContentResolver contentResolver = context.getContentResolver(); 12532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final List<Alarm> alarms = Alarm.getAlarms(contentResolver, null); 1261f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1271f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux final Calendar now = Calendar.getInstance(); 1281f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux for (Alarm alarm : alarms) { 1291f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Remove any instances that may currently exist for the alarm; 1301f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // these aren't relevant on the restore device and we'll recreate them below. 13132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux AlarmStateManager.deleteAllInstances(context, alarm.id); 1321f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1331f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux if (alarm.enabled) { 1341f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Create the next alarm instance to schedule. 1351f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux AlarmInstance alarmInstance = alarm.createInstanceAfter(now); 1361f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1371f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Add the next alarm instance to the database. 13832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux alarmInstance = AlarmInstance.addInstance(contentResolver, alarmInstance); 1391f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1401f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Schedule the next alarm instance in AlarmManager. 14132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux AlarmStateManager.registerInstance(context, alarmInstance, true); 1421f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux LogUtils.i(TAG, "DeskClockBackupAgent scheduled alarm instance: %s", alarmInstance); 1431f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux } 1441f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux } 14532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 14632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Remove the preference to avoid executing this logic multiple times. 14732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux prefs.edit().remove(KEY_RESTORE_FINISHED).apply(); 14832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 14932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux LogUtils.i(TAG, "processRestoredData() completed"); 15032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux return true; 151b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 152b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux}