BackupRestoreConfirmation.java revision a23b343299783e5990370579cfc7d93e62dacb8d
1/*
2 * Copyright (C) 2011 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.backupconfirm;
18
19import android.app.Activity;
20import android.app.backup.FullBackup;
21import android.app.backup.IBackupManager;
22import android.app.backup.IFullBackupRestoreObserver;
23import android.content.Context;
24import android.content.Intent;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.storage.IMountService;
31import android.util.Log;
32import android.util.Slog;
33import android.view.View;
34import android.widget.Button;
35import android.widget.TextView;
36import android.widget.Toast;
37
38/**
39 * Confirm with the user that a requested full backup/restore operation is legitimate.
40 * Any attempt to perform a full backup/restore will launch this UI and wait for a
41 * designated timeout interval (nominally 30 seconds) for the user to confirm.  If the
42 * user fails to respond within the timeout period, or explicitly refuses the operation
43 * within the UI presented here, no data will be transferred off the device.
44 *
45 * Note that the fully scoped name of this class is baked into the backup manager service.
46 *
47 * @hide
48 */
49public class BackupRestoreConfirmation extends Activity {
50    static final String TAG = "BackupRestoreConfirmation";
51    static final boolean DEBUG = true;
52
53    static final String DID_ACKNOWLEDGE = "did_acknowledge";
54
55    static final int MSG_START_BACKUP = 1;
56    static final int MSG_BACKUP_PACKAGE = 2;
57    static final int MSG_END_BACKUP = 3;
58    static final int MSG_START_RESTORE = 11;
59    static final int MSG_RESTORE_PACKAGE = 12;
60    static final int MSG_END_RESTORE = 13;
61    static final int MSG_TIMEOUT = 100;
62
63    Handler mHandler;
64    IBackupManager mBackupManager;
65    IMountService mMountService;
66    FullObserver mObserver;
67    int mToken;
68    boolean mIsEncrypted;
69    boolean mDidAcknowledge;
70
71    TextView mStatusView;
72    TextView mCurPassword;
73    TextView mEncPassword;
74    Button mAllowButton;
75    Button mDenyButton;
76
77    // Handler for dealing with observer callbacks on the main thread
78    class ObserverHandler extends Handler {
79        Context mContext;
80        ObserverHandler(Context context) {
81            mContext = context;
82            mDidAcknowledge = false;
83        }
84
85        @Override
86        public void handleMessage(Message msg) {
87            switch (msg.what) {
88                case MSG_START_BACKUP: {
89                    Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show();
90                }
91                break;
92
93                case MSG_BACKUP_PACKAGE: {
94                    String name = (String) msg.obj;
95                    mStatusView.setText(name);
96                }
97                break;
98
99                case MSG_END_BACKUP: {
100                    Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show();
101                    finish();
102                }
103                break;
104
105                case MSG_START_RESTORE: {
106                    Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show();
107                }
108                break;
109
110                case MSG_RESTORE_PACKAGE: {
111                    String name = (String) msg.obj;
112                    mStatusView.setText(name);
113                }
114                break;
115
116                case MSG_END_RESTORE: {
117                    Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show();
118                    finish();
119                }
120                break;
121
122                case MSG_TIMEOUT: {
123                    Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show();
124                }
125                break;
126            }
127        }
128    }
129
130    @Override
131    public void onCreate(Bundle icicle) {
132        super.onCreate(icicle);
133
134        final Intent intent = getIntent();
135        final String action = intent.getAction();
136
137        final int layoutId;
138        final int titleId;
139        if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) {
140            layoutId = R.layout.confirm_backup;
141            titleId = R.string.backup_confirm_title;
142        } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) {
143            layoutId = R.layout.confirm_restore;
144            titleId = R.string.restore_confirm_title;
145        } else {
146            Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!");
147            finish();
148            return;
149        }
150
151        mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1);
152        if (mToken < 0) {
153            Slog.e(TAG, "Backup/restore confirmation requested but no token passed!");
154            finish();
155            return;
156        }
157
158        mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
159        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
160
161        mHandler = new ObserverHandler(getApplicationContext());
162        final Object oldObserver = getLastNonConfigurationInstance();
163        if (oldObserver == null) {
164            mObserver = new FullObserver(mHandler);
165        } else {
166            mObserver = (FullObserver) oldObserver;
167            mObserver.setHandler(mHandler);
168        }
169
170        setTitle(titleId);
171        setContentView(layoutId);
172
173        // Same resource IDs for each layout variant (backup / restore)
174        mStatusView = (TextView) findViewById(R.id.package_name);
175        mAllowButton = (Button) findViewById(R.id.button_allow);
176        mDenyButton = (Button) findViewById(R.id.button_deny);
177
178        mCurPassword = (TextView) findViewById(R.id.password);
179        mEncPassword = (TextView) findViewById(R.id.enc_password);
180        TextView curPwDesc = (TextView) findViewById(R.id.password_desc);
181
182        // We vary the password prompt depending on whether one is predefined, and whether
183        // the device is encrypted.
184        mIsEncrypted = deviceIsEncrypted();
185        if (mIsEncrypted) {
186            Log.d(TAG, "Device is encrypted: requiring encryption pw");
187            TextView pwPrompt = (TextView) findViewById(R.id.password_desc);
188            // this password is mandatory; we hide the other options during backup
189            if (layoutId == R.layout.confirm_backup) {
190                pwPrompt.setText(R.string.device_encryption_backup_text);
191                TextView tv = (TextView) findViewById(R.id.enc_password);
192                tv.setVisibility(View.GONE);
193                tv = (TextView) findViewById(R.id.enc_password_desc);
194                tv.setVisibility(View.GONE);
195            } else {
196                pwPrompt.setText(R.string.device_encryption_restore_text);
197            }
198        } else if (!haveBackupPassword()) {
199            curPwDesc.setVisibility(View.GONE);
200            mCurPassword.setVisibility(View.GONE);
201            if (layoutId == R.layout.confirm_backup) {
202                TextView encPwDesc = (TextView) findViewById(R.id.enc_password_desc);
203                encPwDesc.setText(R.string.backup_enc_password_optional);
204            }
205        }
206
207        mAllowButton.setOnClickListener(new View.OnClickListener() {
208            @Override
209            public void onClick(View v) {
210                sendAcknowledgement(mToken, true, mObserver);
211                mAllowButton.setEnabled(false);
212                mDenyButton.setEnabled(false);
213            }
214        });
215
216        mDenyButton.setOnClickListener(new View.OnClickListener() {
217            @Override
218            public void onClick(View v) {
219                sendAcknowledgement(mToken, false, mObserver);
220                mAllowButton.setEnabled(false);
221                mDenyButton.setEnabled(false);
222            }
223        });
224
225        // if we're a relaunch we may need to adjust button enable state
226        if (icicle != null) {
227            mDidAcknowledge = icicle.getBoolean(DID_ACKNOWLEDGE, false);
228            mAllowButton.setEnabled(!mDidAcknowledge);
229            mDenyButton.setEnabled(!mDidAcknowledge);
230        }
231    }
232
233    // Preserve the restore observer callback binder across activity relaunch
234    @Override
235    public Object onRetainNonConfigurationInstance() {
236        return mObserver;
237    }
238
239    @Override
240    protected void onSaveInstanceState(Bundle outState) {
241        outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge);
242    }
243
244    void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) {
245        if (!mDidAcknowledge) {
246            mDidAcknowledge = true;
247
248            try {
249                CharSequence encPassword = (mIsEncrypted)
250                        ? mCurPassword.getText() : mEncPassword.getText();
251                mBackupManager.acknowledgeFullBackupOrRestore(mToken,
252                        allow,
253                        String.valueOf(mCurPassword.getText()),
254                        String.valueOf(encPassword),
255                        mObserver);
256            } catch (RemoteException e) {
257                // TODO: bail gracefully if we can't contact the backup manager
258            }
259        }
260    }
261
262    boolean deviceIsEncrypted() {
263        try {
264            return (mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE);
265        } catch (Exception e) {
266            // If we can't talk to the mount service we have a serious problem; fail
267            // "secure" i.e. assuming that the device is encrypted.
268            Slog.e(TAG, "Unable to communicate with mount service: " + e.getMessage());
269            return true;
270        }
271    }
272
273    boolean haveBackupPassword() {
274        try {
275            return mBackupManager.hasBackupPassword();
276        } catch (RemoteException e) {
277            return true;        // in the failure case, assume we need one
278        }
279    }
280
281    /**
282     * The observer binder for showing backup/restore progress.  This binder just bounces
283     * the notifications onto the main thread.
284     */
285    class FullObserver extends IFullBackupRestoreObserver.Stub {
286        private Handler mHandler;
287
288        public FullObserver(Handler h) {
289            mHandler = h;
290        }
291
292        public void setHandler(Handler h) {
293            mHandler = h;
294        }
295
296        //
297        // IFullBackupRestoreObserver implementation
298        //
299        @Override
300        public void onStartBackup() throws RemoteException {
301            mHandler.sendEmptyMessage(MSG_START_BACKUP);
302        }
303
304        @Override
305        public void onBackupPackage(String name) throws RemoteException {
306            mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
307        }
308
309        @Override
310        public void onEndBackup() throws RemoteException {
311            mHandler.sendEmptyMessage(MSG_END_BACKUP);
312        }
313
314        @Override
315        public void onStartRestore() throws RemoteException {
316            mHandler.sendEmptyMessage(MSG_START_RESTORE);
317        }
318
319        @Override
320        public void onRestorePackage(String name) throws RemoteException {
321            mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
322        }
323
324        @Override
325        public void onEndRestore() throws RemoteException {
326            mHandler.sendEmptyMessage(MSG_END_RESTORE);
327        }
328
329        @Override
330        public void onTimeout() throws RemoteException {
331            mHandler.sendEmptyMessage(MSG_TIMEOUT);
332        }
333    }
334}
335