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 }