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; 27b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.os.ParcelFileDescriptor; 2832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieuximport android.os.SystemClock; 29b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport android.support.annotation.NonNull; 30b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 31b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport com.android.deskclock.alarms.AlarmStateManager; 3223629266834a251cb937a885e5223e5ae37cc6faChristine Franksimport com.android.deskclock.data.DataModel; 331f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport com.android.deskclock.provider.Alarm; 341f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport com.android.deskclock.provider.AlarmInstance; 35b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 36b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport java.io.File; 37b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuximport java.io.IOException; 381f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport java.util.Calendar; 391f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieuximport java.util.List; 40b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 41b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieuxpublic class DeskClockBackupAgent extends BackupAgent { 42b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 43310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen private static final LogUtils.Logger LOGGER = new LogUtils.Logger("DeskClockBackupAgent"); 44310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen 4532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux public static final String ACTION_COMPLETE_RESTORE = 4632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux "com.android.deskclock.action.COMPLETE_RESTORE"; 4732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 48b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 49b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 50b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux ParcelFileDescriptor newState) throws IOException { } 51b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 52b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 53b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestore(BackupDataInput data, int appVersionCode, 54b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux ParcelFileDescriptor newState) throws IOException { } 55b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 56b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 57b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestoreFile(@NonNull ParcelFileDescriptor data, long size, File destination, 58b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux int type, long mode, long mtime) throws IOException { 59b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux // The preference file on the backup device may not be the same on the restore device. 60b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux // Massage the file name here before writing it. 61b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux if (destination.getName().endsWith("_preferences.xml")) { 62b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux final String prefFileName = getPackageName() + "_preferences.xml"; 63b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux destination = new File(destination.getParentFile(), prefFileName); 64b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 65b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 66b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux super.onRestoreFile(data, size, destination, type, mode, mtime); 67b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 68b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux 6932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux /** 7032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * When this method is called during backup/restore, the application is executing in a 7132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * "minimalist" state. Because of this, the application's ContentResolver cannot be used. 7232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * Consequently, the work of scheduling alarms on the restore device cannot be done here. 7332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * Instead, a future callback to DeskClock is used as a signal to reschedule the alarms. The 7432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * future callback may take the form of ACTION_BOOT_COMPLETED if the device is not yet fully 7532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * booted (i.e. the restore occurred as part of the setup wizard). If the device is booted, an 7632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * ACTION_COMPLETE_RESTORE broadcast is scheduled 10 seconds in the future to give 7732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * backup/restore enough time to kill the Clock process. Both of these future callbacks result 7832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * in the execution of {@link #processRestoredData(Context)}. 7932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux */ 80b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux @Override 81b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux public void onRestoreFinished() { 82cdfd291b65219b51feaa664db9140d40f5bb5b62Jeff Sharkey if (Utils.isNOrLater()) { 83cdfd291b65219b51feaa664db9140d40f5bb5b62Jeff Sharkey // TODO: migrate restored database and preferences over into 84cdfd291b65219b51feaa664db9140d40f5bb5b62Jeff Sharkey // the device-encrypted storage area 85cdfd291b65219b51feaa664db9140d40f5bb5b62Jeff Sharkey } 86cdfd291b65219b51feaa664db9140d40f5bb5b62Jeff Sharkey 870777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux // Indicate a data restore has been completed. 880777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux DataModel.getDataModel().setRestoreBackupFinished(true); 8932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 9032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Create an Intent to send into DeskClock indicating restore is complete. 9132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final PendingIntent restoreIntent = PendingIntent.getBroadcast(this, 0, 9232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux new Intent(ACTION_COMPLETE_RESTORE).setClass(this, AlarmInitReceiver.class), 9332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); 9432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 9532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Deliver the Intent 10 seconds from now. 9632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final long triggerAtMillis = SystemClock.elapsedRealtime() + 10000; 9732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 9832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Schedule the Intent delivery in AlarmManager. 9932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 10032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, restoreIntent); 10132e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 102310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen LOGGER.i("Waiting for %s to complete the data restore", ACTION_COMPLETE_RESTORE); 10332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux } 10432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 10532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux /** 10632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * @param context a context to access resources and services 10732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux * @return {@code true} if restore data was processed; {@code false} otherwise. 10832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux */ 10932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux public static boolean processRestoredData(Context context) { 1100777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux // If data was not recently restored, there is nothing to do. 1110777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux if (!DataModel.getDataModel().isRestoreBackupFinished()) { 11232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux return false; 11332e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux } 11432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 115310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen LOGGER.i("processRestoredData() started"); 11632e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 11732e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Now that alarms have been restored, schedule new instances in AlarmManager. 11832e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final ContentResolver contentResolver = context.getContentResolver(); 11932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux final List<Alarm> alarms = Alarm.getAlarms(contentResolver, null); 1201f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1211f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux final Calendar now = Calendar.getInstance(); 1221f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux for (Alarm alarm : alarms) { 1231f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Remove any instances that may currently exist for the alarm; 1241f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // these aren't relevant on the restore device and we'll recreate them below. 12532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux AlarmStateManager.deleteAllInstances(context, alarm.id); 1261f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1271f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux if (alarm.enabled) { 1281f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Create the next alarm instance to schedule. 1291f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux AlarmInstance alarmInstance = alarm.createInstanceAfter(now); 1301f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1311f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Add the next alarm instance to the database. 13232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux alarmInstance = AlarmInstance.addInstance(contentResolver, alarmInstance); 1331f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux 1341f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux // Schedule the next alarm instance in AlarmManager. 13532e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux AlarmStateManager.registerInstance(context, alarmInstance, true); 136310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen LOGGER.i("DeskClockBackupAgent scheduled alarm instance: %s", alarmInstance); 1371f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux } 1381f53186f7d1d449382b2e429cd35e3312f9eb97eJames Lemieux } 13932e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 14032e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux // Remove the preference to avoid executing this logic multiple times. 1410777a082e248c33c2e5a32690bfcdbd5de7d3f9eJames Lemieux DataModel.getDataModel().setRestoreBackupFinished(false); 14232e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux 143310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen LOGGER.i("processRestoredData() completed"); 14432e2e0440cc4219dfe200bcf025508b740ec7cb1James Lemieux return true; 145b5f116935f6b767d9bf7c266a9c189d706cc6d66James Lemieux } 146310210dab3e97b1defe0870ecbc25b1451e90392Justin Klaassen} 147