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