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