1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.deskclock 18 19 import android.app.AlarmManager 20 import android.app.PendingIntent 21 import android.app.backup.BackupAgent 22 import android.app.backup.BackupDataInput 23 import android.app.backup.BackupDataOutput 24 import android.content.Context 25 import android.content.Intent 26 import android.os.ParcelFileDescriptor 27 import android.os.SystemClock 28 29 import com.android.deskclock.alarms.AlarmStateManager 30 import com.android.deskclock.data.DataModel 31 import com.android.deskclock.provider.Alarm 32 import com.android.deskclock.provider.AlarmInstance 33 34 import java.io.File 35 import java.io.IOException 36 import java.util.Calendar 37 38 class DeskClockBackupAgent : BackupAgent() { 39 @Throws(IOException::class) onBackupnull40 override fun onBackup( 41 oldState: ParcelFileDescriptor, 42 data: BackupDataOutput, 43 newState: ParcelFileDescriptor 44 ) { 45 } 46 47 @Throws(IOException::class) onRestorenull48 override fun onRestore( 49 data: BackupDataInput, 50 appVersionCode: Int, 51 newState: ParcelFileDescriptor 52 ) { 53 } 54 55 @Throws(IOException::class) onRestoreFilenull56 override fun onRestoreFile( 57 data: ParcelFileDescriptor, 58 size: Long, 59 destination: File, 60 type: Int, 61 mode: Long, 62 mtime: Long 63 ) { 64 // The preference file on the backup device may not be the same on the restore device. 65 // Massage the file name here before writing it. 66 var variableDestination = destination 67 if (variableDestination.name.endsWith("_preferences.xml")) { 68 val prefFileName = packageName + "_preferences.xml" 69 variableDestination = File(variableDestination.parentFile, prefFileName) 70 } 71 72 super.onRestoreFile(data, size, variableDestination, type, mode, mtime) 73 } 74 75 /** 76 * When this method is called during backup/restore, the application is executing in a 77 * "minimalist" state. Because of this, the application's ContentResolver cannot be used. 78 * Consequently, the work of scheduling alarms on the restore device cannot be done here. 79 * Instead, a future callback to DeskClock is used as a signal to reschedule the alarms. The 80 * future callback may take the form of ACTION_BOOT_COMPLETED if the device is not yet fully 81 * booted (i.e. the restore occurred as part of the setup wizard). If the device is booted, an 82 * ACTION_COMPLETE_RESTORE broadcast is scheduled 10 seconds in the future to give 83 * backup/restore enough time to kill the Clock process. Both of these future callbacks result 84 * in the execution of [.processRestoredData]. 85 */ onRestoreFinishednull86 override fun onRestoreFinished() { 87 if (Utils.isNOrLater) { 88 // TODO: migrate restored database and preferences over into 89 // the device-encrypted storage area 90 } 91 92 // Indicate a data restore has been completed. 93 DataModel.dataModel.isRestoreBackupFinished = true 94 95 // Create an Intent to send into DeskClock indicating restore is complete. 96 val restoreIntent = PendingIntent.getBroadcast(this, 0, 97 Intent(ACTION_COMPLETE_RESTORE).setClass(this, AlarmInitReceiver::class.java), 98 PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_CANCEL_CURRENT) 99 100 // Deliver the Intent 10 seconds from now. 101 val triggerAtMillis = SystemClock.elapsedRealtime() + 10000 102 103 // Schedule the Intent delivery in AlarmManager. 104 val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager 105 alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, restoreIntent) 106 107 LOGGER.i("Waiting for %s to complete the data restore", ACTION_COMPLETE_RESTORE) 108 } 109 110 companion object { 111 private val LOGGER = LogUtils.Logger("DeskClockBackupAgent") 112 113 const val ACTION_COMPLETE_RESTORE = "com.android.deskclock.action.COMPLETE_RESTORE" 114 115 /** 116 * @param context a context to access resources and services 117 * @return `true` if restore data was processed; `false` otherwise. 118 */ 119 @JvmStatic processRestoredDatanull120 fun processRestoredData(context: Context): Boolean { 121 // If data was not recently restored, there is nothing to do. 122 if (!DataModel.dataModel.isRestoreBackupFinished) { 123 return false 124 } 125 126 LOGGER.i("processRestoredData() started") 127 128 // Now that alarms have been restored, schedule new instances in AlarmManager. 129 val contentResolver = context.contentResolver 130 val alarms = Alarm.getAlarms(contentResolver, null) 131 132 val now = Calendar.getInstance() 133 for (alarm in alarms) { 134 // Remove any instances that may currently exist for the alarm; 135 // these aren't relevant on the restore device and we'll recreate them below. 136 AlarmStateManager.deleteAllInstances(context, alarm.id) 137 138 if (alarm.enabled) { 139 // Create the next alarm instance to schedule. 140 var alarmInstance = alarm.createInstanceAfter(now) 141 142 // Add the next alarm instance to the database. 143 alarmInstance = AlarmInstance.addInstance(contentResolver, alarmInstance) 144 145 // Schedule the next alarm instance in AlarmManager. 146 AlarmStateManager.registerInstance(context, alarmInstance, true) 147 LOGGER.i("DeskClockBackupAgent scheduled alarm instance: %s", alarmInstance) 148 } 149 } 150 151 // Remove the preference to avoid executing this logic multiple times. 152 DataModel.dataModel.isRestoreBackupFinished = false 153 154 LOGGER.i("processRestoredData() completed") 155 return true 156 } 157 } 158 }