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.TAG;
20
21import android.annotation.Nullable;
22import android.app.AlarmManager;
23import android.app.backup.BackupTransport;
24import android.app.backup.IBackupObserver;
25import android.os.RemoteException;
26import android.os.SystemClock;
27import android.util.EventLog;
28import android.util.Slog;
29
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.internal.backup.IBackupTransport;
32import com.android.server.EventLogTags;
33import com.android.server.backup.BackupManagerService;
34import com.android.server.backup.TransportManager;
35import com.android.server.backup.transport.TransportClient;
36
37import java.io.File;
38import java.util.ArrayList;
39import java.util.List;
40
41/**
42 * Attempts to call {@link BackupTransport#initializeDevice()} followed by
43 * {@link BackupTransport#finishBackup()} for the transport names passed in with the intent of
44 * wiping backup data from the transport.
45 *
46 * If the transport returns error, it will record the operation as pending and schedule it to run in
47 * a future time according to {@link BackupTransport#requestBackupTime()}. The result status
48 * reported to observers will be the last unsuccessful status reported by the transports. If every
49 * operation was successful then it's {@link BackupTransport#TRANSPORT_OK}.
50 */
51public class PerformInitializeTask implements Runnable {
52    private final BackupManagerService mBackupManagerService;
53    private final TransportManager mTransportManager;
54    private final String[] mQueue;
55    private final File mBaseStateDir;
56    private final OnTaskFinishedListener mListener;
57    @Nullable private IBackupObserver mObserver;
58
59    public PerformInitializeTask(
60            BackupManagerService backupManagerService,
61            String[] transportNames,
62            @Nullable IBackupObserver observer,
63            OnTaskFinishedListener listener) {
64        this(
65                backupManagerService,
66                backupManagerService.getTransportManager(),
67                transportNames,
68                observer,
69                listener,
70                backupManagerService.getBaseStateDir());
71    }
72
73    @VisibleForTesting
74    PerformInitializeTask(
75            BackupManagerService backupManagerService,
76            TransportManager transportManager,
77            String[] transportNames,
78            @Nullable IBackupObserver observer,
79            OnTaskFinishedListener listener,
80            File baseStateDir) {
81        mBackupManagerService = backupManagerService;
82        mTransportManager = transportManager;
83        mQueue = transportNames;
84        mObserver = observer;
85        mListener = listener;
86        mBaseStateDir = baseStateDir;
87    }
88
89    private void notifyResult(String target, int status) {
90        try {
91            if (mObserver != null) {
92                mObserver.onResult(target, status);
93            }
94        } catch (RemoteException ignored) {
95            mObserver = null;       // don't try again
96        }
97    }
98
99    private void notifyFinished(int status) {
100        try {
101            if (mObserver != null) {
102                mObserver.backupFinished(status);
103            }
104        } catch (RemoteException ignored) {
105            mObserver = null;
106        }
107    }
108
109    public void run() {
110        // mWakelock is *acquired* when execution begins here
111        String callerLogString = "PerformInitializeTask.run()";
112        List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length);
113        int result = BackupTransport.TRANSPORT_OK;
114        try {
115            for (String transportName : mQueue) {
116                TransportClient transportClient =
117                        mTransportManager.getTransportClient(transportName, callerLogString);
118                if (transportClient == null) {
119                    Slog.e(TAG, "Requested init for " + transportName + " but not found");
120                    continue;
121                }
122                transportClientsToDisposeOf.add(transportClient);
123
124                Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
125                String transportDirName =
126                        mTransportManager.getTransportDirName(
127                                transportClient.getTransportComponent());
128                EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
129                long startRealtime = SystemClock.elapsedRealtime();
130
131                IBackupTransport transport = transportClient.connectOrThrow(callerLogString);
132                int status = transport.initializeDevice();
133
134                if (status == BackupTransport.TRANSPORT_OK) {
135                    status = transport.finishBackup();
136                }
137
138                // Okay, the wipe really happened.  Clean up our local bookkeeping.
139                if (status == BackupTransport.TRANSPORT_OK) {
140                    Slog.i(TAG, "Device init successful");
141                    int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
142                    EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
143                    File stateFileDir = new File(mBaseStateDir, transportDirName);
144                    mBackupManagerService.resetBackupState(stateFileDir);
145                    EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
146                    mBackupManagerService.recordInitPending(false, transportName, transportDirName);
147                    notifyResult(transportName, BackupTransport.TRANSPORT_OK);
148                } else {
149                    // If this didn't work, requeue this one and try again
150                    // after a suitable interval
151                    Slog.e(TAG, "Transport error in initializeDevice()");
152                    EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
153                    mBackupManagerService.recordInitPending(true, transportName, transportDirName);
154                    notifyResult(transportName, status);
155                    result = status;
156
157                    // do this via another alarm to make sure of the wakelock states
158                    long delay = transport.requestBackupTime();
159                    Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay);
160                    mBackupManagerService.getAlarmManager().set(
161                            AlarmManager.RTC_WAKEUP,
162                            System.currentTimeMillis() + delay,
163                            mBackupManagerService.getRunInitIntent());
164                }
165            }
166        } catch (Exception e) {
167            Slog.e(TAG, "Unexpected error performing init", e);
168            result = BackupTransport.TRANSPORT_ERROR;
169        } finally {
170            for (TransportClient transportClient : transportClientsToDisposeOf) {
171                mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
172            }
173            notifyFinished(result);
174            mListener.onFinished(callerLogString);
175        }
176    }
177}
178