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