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