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