1 /*
2  * Copyright (C) 2017 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.server.backup.internal;
18 
19 import static com.android.server.backup.BackupManagerService.DEBUG;
20 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
21 import static com.android.server.backup.BackupManagerService.TAG;
22 
23 import android.app.backup.RestoreSet;
24 import android.content.Intent;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.util.EventLog;
31 import android.util.Pair;
32 import android.util.Slog;
33 
34 import com.android.internal.backup.IBackupTransport;
35 import com.android.internal.util.Preconditions;
36 import com.android.server.EventLogTags;
37 import com.android.server.backup.BackupAgentTimeoutParameters;
38 import com.android.server.backup.BackupRestoreTask;
39 import com.android.server.backup.DataChangedJournal;
40 import com.android.server.backup.TransportManager;
41 import com.android.server.backup.UserBackupManagerService;
42 import com.android.server.backup.fullbackup.PerformAdbBackupTask;
43 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
44 import com.android.server.backup.keyvalue.BackupRequest;
45 import com.android.server.backup.keyvalue.KeyValueBackupTask;
46 import com.android.server.backup.params.AdbBackupParams;
47 import com.android.server.backup.params.AdbParams;
48 import com.android.server.backup.params.AdbRestoreParams;
49 import com.android.server.backup.params.BackupParams;
50 import com.android.server.backup.params.ClearParams;
51 import com.android.server.backup.params.ClearRetryParams;
52 import com.android.server.backup.params.RestoreGetSetsParams;
53 import com.android.server.backup.params.RestoreParams;
54 import com.android.server.backup.restore.PerformAdbRestoreTask;
55 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
56 import com.android.server.backup.transport.TransportClient;
57 
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
61 
62 /**
63  * Asynchronous backup/restore handler thread.
64  */
65 public class BackupHandler extends Handler {
66 
67     public static final int MSG_RUN_BACKUP = 1;
68     public static final int MSG_RUN_ADB_BACKUP = 2;
69     public static final int MSG_RUN_RESTORE = 3;
70     public static final int MSG_RUN_CLEAR = 4;
71     public static final int MSG_RUN_GET_RESTORE_SETS = 6;
72     public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
73     public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
74     public static final int MSG_RUN_ADB_RESTORE = 10;
75     public static final int MSG_RETRY_INIT = 11;
76     public static final int MSG_RETRY_CLEAR = 12;
77     public static final int MSG_WIDGET_BROADCAST = 13;
78     public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
79     public static final int MSG_REQUEST_BACKUP = 15;
80     public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
81     public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
82     public static final int MSG_RESTORE_OPERATION_TIMEOUT = 18;
83     // backup task state machine tick
84     public static final int MSG_BACKUP_RESTORE_STEP = 20;
85     public static final int MSG_OP_COMPLETE = 21;
86 
87     private final UserBackupManagerService backupManagerService;
88     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
89 
BackupHandler(UserBackupManagerService backupManagerService, Looper looper)90     public BackupHandler(UserBackupManagerService backupManagerService, Looper looper) {
91         super(looper);
92         this.backupManagerService = backupManagerService;
93         mAgentTimeoutParameters = Preconditions.checkNotNull(
94                 backupManagerService.getAgentTimeoutParameters(),
95                 "Timeout parameters cannot be null");
96     }
97 
handleMessage(Message msg)98     public void handleMessage(Message msg) {
99 
100         TransportManager transportManager = backupManagerService.getTransportManager();
101         switch (msg.what) {
102             case MSG_RUN_BACKUP: {
103                 backupManagerService.setLastBackupPass(System.currentTimeMillis());
104 
105                 String callerLogString = "BH/MSG_RUN_BACKUP";
106                 TransportClient transportClient =
107                         transportManager.getCurrentTransportClient(callerLogString);
108                 IBackupTransport transport =
109                         transportClient != null
110                                 ? transportClient.connect(callerLogString)
111                                 : null;
112                 if (transport == null) {
113                     if (transportClient != null) {
114                         transportManager
115                                 .disposeOfTransportClient(transportClient, callerLogString);
116                     }
117                     Slog.v(TAG, "Backup requested but no transport available");
118                     synchronized (backupManagerService.getQueueLock()) {
119                         backupManagerService.setBackupRunning(false);
120                     }
121                     backupManagerService.getWakelock().release();
122                     break;
123                 }
124 
125                 // Snapshot the pending-backup set and work on that.
126                 List<String> queue = new ArrayList<>();
127                 DataChangedJournal oldJournal = backupManagerService.getJournal();
128                 synchronized (backupManagerService.getQueueLock()) {
129                     // Do we have any work to do?  Construct the work queue
130                     // then release the synchronization lock to actually run
131                     // the backup.
132                     if (backupManagerService.getPendingBackups().size() > 0) {
133                         for (BackupRequest b : backupManagerService.getPendingBackups().values()) {
134                             queue.add(b.packageName);
135                         }
136                         if (DEBUG) {
137                             Slog.v(TAG, "clearing pending backups");
138                         }
139                         backupManagerService.getPendingBackups().clear();
140 
141                         // Start a new backup-queue journal file too
142                         backupManagerService.setJournal(null);
143 
144                     }
145                 }
146 
147                 // At this point, we have started a new journal file, and the old
148                 // file identity is being passed to the backup processing task.
149                 // When it completes successfully, that old journal file will be
150                 // deleted.  If we crash prior to that, the old journal is parsed
151                 // at next boot and the journaled requests fulfilled.
152                 boolean staged = true;
153                 if (queue.size() > 0) {
154                     // Spin up a backup state sequence and set it running
155                     try {
156                         OnTaskFinishedListener listener =
157                                 caller ->
158                                         transportManager
159                                                 .disposeOfTransportClient(transportClient, caller);
160                         KeyValueBackupTask.start(
161                                 backupManagerService,
162                                 transportClient,
163                                 transport.transportDirName(),
164                                 queue,
165                                 oldJournal,
166                                 /* observer */ null,
167                                 /* monitor */ null,
168                                 listener,
169                                 Collections.emptyList(),
170                                 /* userInitiated */ false,
171                                 /* nonIncremental */ false);
172                     } catch (Exception e) {
173                         // unable to ask the transport its dir name -- transient failure, since
174                         // the above check succeeded.  Try again next time.
175                         Slog.e(TAG, "Transport became unavailable attempting backup"
176                                 + " or error initializing backup task", e);
177                         staged = false;
178                     }
179                 } else {
180                     Slog.v(TAG, "Backup requested but nothing pending");
181                     staged = false;
182                 }
183 
184                 if (!staged) {
185                     transportManager.disposeOfTransportClient(transportClient, callerLogString);
186                     // if we didn't actually hand off the wakelock, rewind until next time
187                     synchronized (backupManagerService.getQueueLock()) {
188                         backupManagerService.setBackupRunning(false);
189                     }
190                     backupManagerService.getWakelock().release();
191                 }
192                 break;
193             }
194 
195             case MSG_BACKUP_RESTORE_STEP: {
196                 try {
197                     BackupRestoreTask task = (BackupRestoreTask) msg.obj;
198                     if (MORE_DEBUG) {
199                         Slog.v(TAG, "Got next step for " + task + ", executing");
200                     }
201                     task.execute();
202                 } catch (ClassCastException e) {
203                     Slog.e(TAG, "Invalid backup/restore task in flight, obj=" + msg.obj);
204                 }
205                 break;
206             }
207 
208             case MSG_OP_COMPLETE: {
209                 try {
210                     Pair<BackupRestoreTask, Long> taskWithResult =
211                             (Pair<BackupRestoreTask, Long>) msg.obj;
212                     taskWithResult.first.operationComplete(taskWithResult.second);
213                 } catch (ClassCastException e) {
214                     Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
215                 }
216                 break;
217             }
218 
219             case MSG_RUN_ADB_BACKUP: {
220                 // TODO: refactor full backup to be a looper-based state machine
221                 // similar to normal backup/restore.
222                 AdbBackupParams params = (AdbBackupParams) msg.obj;
223                 PerformAdbBackupTask task = new PerformAdbBackupTask(backupManagerService,
224                         params.fd,
225                         params.observer, params.includeApks, params.includeObbs,
226                         params.includeShared, params.doWidgets, params.curPassword,
227                         params.encryptPassword, params.allApps, params.includeSystem,
228                         params.doCompress, params.includeKeyValue, params.packages, params.latch);
229                 (new Thread(task, "adb-backup")).start();
230                 break;
231             }
232 
233             case MSG_RUN_FULL_TRANSPORT_BACKUP: {
234                 PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
235                 (new Thread(task, "transport-backup")).start();
236                 break;
237             }
238 
239             case MSG_RUN_RESTORE: {
240                 RestoreParams params = (RestoreParams) msg.obj;
241                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
242 
243                 PerformUnifiedRestoreTask task =
244                         new PerformUnifiedRestoreTask(
245                                 backupManagerService,
246                                 params.transportClient,
247                                 params.observer,
248                                 params.monitor,
249                                 params.token,
250                                 params.packageInfo,
251                                 params.pmToken,
252                                 params.isSystemRestore,
253                                 params.filterSet,
254                                 params.listener);
255 
256                 synchronized (backupManagerService.getPendingRestores()) {
257                     if (backupManagerService.isRestoreInProgress()) {
258                         if (DEBUG) {
259                             Slog.d(TAG, "Restore in progress, queueing.");
260                         }
261                         backupManagerService.getPendingRestores().add(task);
262                         // This task will be picked up and executed when the the currently running
263                         // restore task finishes.
264                     } else {
265                         if (DEBUG) {
266                             Slog.d(TAG, "Starting restore.");
267                         }
268                         backupManagerService.setRestoreInProgress(true);
269                         Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
270                         sendMessage(restoreMsg);
271                     }
272                 }
273                 break;
274             }
275 
276             case MSG_RUN_ADB_RESTORE: {
277                 // TODO: refactor full restore to be a looper-based state machine
278                 // similar to normal backup/restore.
279                 AdbRestoreParams params = (AdbRestoreParams) msg.obj;
280                 PerformAdbRestoreTask task = new PerformAdbRestoreTask(backupManagerService,
281                         params.fd,
282                         params.curPassword, params.encryptPassword,
283                         params.observer, params.latch);
284                 (new Thread(task, "adb-restore")).start();
285                 break;
286             }
287 
288             case MSG_RUN_CLEAR: {
289                 ClearParams params = (ClearParams) msg.obj;
290                 Runnable task =
291                         new PerformClearTask(
292                                 backupManagerService,
293                                 params.transportClient,
294                                 params.packageInfo,
295                                 params.listener);
296                 task.run();
297                 break;
298             }
299 
300             case MSG_RETRY_CLEAR: {
301                 // reenqueues if the transport remains unavailable
302                 ClearRetryParams params = (ClearRetryParams) msg.obj;
303                 backupManagerService.clearBackupData(params.transportName, params.packageName);
304                 break;
305             }
306 
307             case MSG_RUN_GET_RESTORE_SETS: {
308                 // Like other async operations, this is entered with the wakelock held
309                 RestoreSet[] sets = null;
310                 RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
311                 String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
312                 try {
313                     IBackupTransport transport =
314                             params.transportClient.connectOrThrow(callerLogString);
315                     sets = transport.getAvailableRestoreSets();
316                     // cache the result in the active session
317                     synchronized (params.session) {
318                         params.session.setRestoreSets(sets);
319                     }
320                     if (sets == null) {
321                         EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
322                     }
323                 } catch (Exception e) {
324                     Slog.e(TAG, "Error from transport getting set list: " + e.getMessage());
325                 } finally {
326                     if (params.observer != null) {
327                         try {
328                             params.observer.restoreSetsAvailable(sets);
329                         } catch (RemoteException re) {
330                             Slog.e(TAG, "Unable to report listing to observer");
331                         } catch (Exception e) {
332                             Slog.e(TAG, "Restore observer threw: " + e.getMessage());
333                         }
334                     }
335 
336                     // Done: reset the session timeout clock
337                     removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
338                     sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
339                             mAgentTimeoutParameters.getRestoreAgentTimeoutMillis());
340 
341                     params.listener.onFinished(callerLogString);
342                 }
343                 break;
344             }
345 
346             case MSG_BACKUP_OPERATION_TIMEOUT:
347             case MSG_RESTORE_OPERATION_TIMEOUT: {
348                 Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
349                 backupManagerService.handleCancel(msg.arg1, false);
350                 break;
351             }
352 
353             case MSG_RESTORE_SESSION_TIMEOUT: {
354                 synchronized (backupManagerService) {
355                     if (backupManagerService.getActiveRestoreSession() != null) {
356                         // Client app left the restore session dangling.  We know that it
357                         // can't be in the middle of an actual restore operation because
358                         // the timeout is suspended while a restore is in progress.  Clean
359                         // up now.
360                         Slog.w(TAG, "Restore session timed out; aborting");
361                         backupManagerService.getActiveRestoreSession().markTimedOut();
362                         post(backupManagerService.getActiveRestoreSession().new EndRestoreRunnable(
363                                 backupManagerService,
364                                 backupManagerService.getActiveRestoreSession()));
365                     }
366                 }
367                 break;
368             }
369 
370             case MSG_FULL_CONFIRMATION_TIMEOUT: {
371                 synchronized (backupManagerService.getAdbBackupRestoreConfirmations()) {
372                     AdbParams params = backupManagerService.getAdbBackupRestoreConfirmations().get(
373                             msg.arg1);
374                     if (params != null) {
375                         Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
376 
377                         // Release the waiter; timeout == completion
378                         backupManagerService.signalAdbBackupRestoreCompletion(params);
379 
380                         // Remove the token from the set
381                         backupManagerService.getAdbBackupRestoreConfirmations().delete(msg.arg1);
382 
383                         // Report a timeout to the observer, if any
384                         if (params.observer != null) {
385                             try {
386                                 params.observer.onTimeout();
387                             } catch (RemoteException e) {
388                             /* don't care if the app has gone away */
389                             }
390                         }
391                     } else {
392                         Slog.d(TAG, "couldn't find params for token " + msg.arg1);
393                     }
394                 }
395                 break;
396             }
397 
398             case MSG_WIDGET_BROADCAST: {
399                 final Intent intent = (Intent) msg.obj;
400                 backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
401                 break;
402             }
403 
404             case MSG_REQUEST_BACKUP: {
405                 BackupParams params = (BackupParams) msg.obj;
406                 if (MORE_DEBUG) {
407                     Slog.d(TAG, "MSG_REQUEST_BACKUP observer=" + params.observer);
408                 }
409                 backupManagerService.setBackupRunning(true);
410                 backupManagerService.getWakelock().acquire();
411 
412                 KeyValueBackupTask.start(
413                         backupManagerService,
414                         params.transportClient,
415                         params.dirName,
416                         params.kvPackages,
417                         /* dataChangedJournal */ null,
418                         params.observer,
419                         params.monitor,
420                         params.listener,
421                         params.fullPackages,
422                         /* userInitiated */ true,
423                         params.nonIncrementalBackup);
424                 break;
425             }
426 
427             case MSG_SCHEDULE_BACKUP_PACKAGE: {
428                 String pkgName = (String) msg.obj;
429                 if (MORE_DEBUG) {
430                     Slog.d(TAG, "MSG_SCHEDULE_BACKUP_PACKAGE " + pkgName);
431                 }
432                 backupManagerService.dataChangedImpl(pkgName);
433                 break;
434             }
435         }
436     }
437 }
438