blob: 7f1d05951e95a5e03f337a9e7a6f769bf13a0959 [file] [log] [blame]
Christopher Tate4a627c72011-04-01 14:43:32 -07001/*
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;
Christopher Tate4a627c72011-04-01 14:43:32 -070027import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
Christopher Tate32418be2011-10-10 13:51:12 -070030import android.os.storage.IMountService;
31import android.util.Log;
Christopher Tate4a627c72011-04-01 14:43:32 -070032import 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
Christopher Tate3851fa92011-08-05 12:28:15 -070053 static final String DID_ACKNOWLEDGE = "did_acknowledge";
54
Christopher Tate4a627c72011-04-01 14:43:32 -070055 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;
Christopher Tate32418be2011-10-10 13:51:12 -070065 IMountService mMountService;
Christopher Tate4a627c72011-04-01 14:43:32 -070066 FullObserver mObserver;
67 int mToken;
Christopher Tate32418be2011-10-10 13:51:12 -070068 boolean mIsEncrypted;
Christopher Tateec5d4a02011-06-07 15:35:45 -070069 boolean mDidAcknowledge;
Christopher Tate4a627c72011-04-01 14:43:32 -070070
71 TextView mStatusView;
Christopher Tate728a1c42011-07-28 18:03:03 -070072 TextView mCurPassword;
73 TextView mEncPassword;
Christopher Tate4a627c72011-04-01 14:43:32 -070074 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;
Christopher Tateec5d4a02011-06-07 15:35:45 -070082 mDidAcknowledge = false;
Christopher Tate4a627c72011-04-01 14:43:32 -070083 }
84
85 @Override
86 public void handleMessage(Message msg) {
87 switch (msg.what) {
88 case MSG_START_BACKUP: {
Christopher Tate28b591c2011-09-14 17:34:52 -070089 Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show();
Christopher Tate4a627c72011-04-01 14:43:32 -070090 }
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: {
Christopher Tate28b591c2011-09-14 17:34:52 -0700100 Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show();
Christopher Tatedc92c822011-05-13 15:38:02 -0700101 finish();
Christopher Tate4a627c72011-04-01 14:43:32 -0700102 }
103 break;
104
105 case MSG_START_RESTORE: {
Christopher Tate28b591c2011-09-14 17:34:52 -0700106 Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show();
Christopher Tate4a627c72011-04-01 14:43:32 -0700107 }
108 break;
109
110 case MSG_RESTORE_PACKAGE: {
Christopher Tate75a99702011-05-18 16:28:19 -0700111 String name = (String) msg.obj;
112 mStatusView.setText(name);
Christopher Tate4a627c72011-04-01 14:43:32 -0700113 }
114 break;
115
116 case MSG_END_RESTORE: {
Christopher Tate28b591c2011-09-14 17:34:52 -0700117 Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show();
Christopher Tatedc92c822011-05-13 15:38:02 -0700118 finish();
Christopher Tate4a627c72011-04-01 14:43:32 -0700119 }
120 break;
121
122 case MSG_TIMEOUT: {
Christopher Tate28b591c2011-09-14 17:34:52 -0700123 Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show();
Christopher Tate4a627c72011-04-01 14:43:32 -0700124 }
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
Christopher Tate2efd2db2011-07-19 16:32:49 -0700137 final int layoutId;
Christopher Tatec58cf7d2011-09-13 17:51:18 -0700138 final int titleId;
Christopher Tate4a627c72011-04-01 14:43:32 -0700139 if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) {
140 layoutId = R.layout.confirm_backup;
Christopher Tatec58cf7d2011-09-13 17:51:18 -0700141 titleId = R.string.backup_confirm_title;
Christopher Tate4a627c72011-04-01 14:43:32 -0700142 } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) {
143 layoutId = R.layout.confirm_restore;
Christopher Tatec58cf7d2011-09-13 17:51:18 -0700144 titleId = R.string.restore_confirm_title;
Christopher Tate4a627c72011-04-01 14:43:32 -0700145 } 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));
Christopher Tate32418be2011-10-10 13:51:12 -0700159 mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
Christopher Tate4a627c72011-04-01 14:43:32 -0700160
161 mHandler = new ObserverHandler(getApplicationContext());
Christopher Tate3851fa92011-08-05 12:28:15 -0700162 final Object oldObserver = getLastNonConfigurationInstance();
163 if (oldObserver == null) {
164 mObserver = new FullObserver(mHandler);
165 } else {
166 mObserver = (FullObserver) oldObserver;
167 mObserver.setHandler(mHandler);
168 }
Christopher Tate4a627c72011-04-01 14:43:32 -0700169
Christopher Tatec58cf7d2011-09-13 17:51:18 -0700170 setTitle(titleId);
Christopher Tate4a627c72011-04-01 14:43:32 -0700171 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
Christopher Tate728a1c42011-07-28 18:03:03 -0700178 mCurPassword = (TextView) findViewById(R.id.password);
179 mEncPassword = (TextView) findViewById(R.id.enc_password);
180 TextView curPwDesc = (TextView) findViewById(R.id.password_desc);
181
Christopher Tate32418be2011-10-10 13:51:12 -0700182 // 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()) {
Christopher Tate728a1c42011-07-28 18:03:03 -0700199 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);
Christopher Tate2efd2db2011-07-19 16:32:49 -0700204 }
205 }
206
Christopher Tate4a627c72011-04-01 14:43:32 -0700207 mAllowButton.setOnClickListener(new View.OnClickListener() {
208 @Override
209 public void onClick(View v) {
Christopher Tateec5d4a02011-06-07 15:35:45 -0700210 sendAcknowledgement(mToken, true, mObserver);
Christopher Tate4a627c72011-04-01 14:43:32 -0700211 mAllowButton.setEnabled(false);
212 mDenyButton.setEnabled(false);
213 }
214 });
215
216 mDenyButton.setOnClickListener(new View.OnClickListener() {
217 @Override
218 public void onClick(View v) {
Christopher Tateec5d4a02011-06-07 15:35:45 -0700219 sendAcknowledgement(mToken, false, mObserver);
Christopher Tate4a627c72011-04-01 14:43:32 -0700220 mAllowButton.setEnabled(false);
221 mDenyButton.setEnabled(false);
222 }
223 });
Christopher Tate3851fa92011-08-05 12:28:15 -0700224
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;
Christopher Tate4a627c72011-04-01 14:43:32 -0700237 }
238
239 @Override
Christopher Tate3851fa92011-08-05 12:28:15 -0700240 protected void onSaveInstanceState(Bundle outState) {
241 outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge);
Christopher Tate4a627c72011-04-01 14:43:32 -0700242 }
243
Christopher Tateec5d4a02011-06-07 15:35:45 -0700244 void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) {
245 if (!mDidAcknowledge) {
246 mDidAcknowledge = true;
Christopher Tate2efd2db2011-07-19 16:32:49 -0700247
Christopher Tateec5d4a02011-06-07 15:35:45 -0700248 try {
Christopher Tate32418be2011-10-10 13:51:12 -0700249 CharSequence encPassword = (mIsEncrypted)
250 ? mCurPassword.getText() : mEncPassword.getText();
Christopher Tate728a1c42011-07-28 18:03:03 -0700251 mBackupManager.acknowledgeFullBackupOrRestore(mToken,
252 allow,
253 String.valueOf(mCurPassword.getText()),
Christopher Tate32418be2011-10-10 13:51:12 -0700254 String.valueOf(encPassword),
Christopher Tate728a1c42011-07-28 18:03:03 -0700255 mObserver);
Christopher Tateec5d4a02011-06-07 15:35:45 -0700256 } catch (RemoteException e) {
257 // TODO: bail gracefully if we can't contact the backup manager
258 }
259 }
260 }
261
Christopher Tate32418be2011-10-10 13:51:12 -0700262 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 return true;
269 }
270 }
271
Christopher Tate728a1c42011-07-28 18:03:03 -0700272 boolean haveBackupPassword() {
273 try {
274 return mBackupManager.hasBackupPassword();
275 } catch (RemoteException e) {
276 return true; // in the failure case, assume we need one
277 }
278 }
279
Christopher Tate4a627c72011-04-01 14:43:32 -0700280 /**
281 * The observer binder for showing backup/restore progress. This binder just bounces
282 * the notifications onto the main thread.
283 */
284 class FullObserver extends IFullBackupRestoreObserver.Stub {
Christopher Tate3851fa92011-08-05 12:28:15 -0700285 private Handler mHandler;
286
287 public FullObserver(Handler h) {
288 mHandler = h;
289 }
290
291 public void setHandler(Handler h) {
292 mHandler = h;
293 }
294
Christopher Tate4a627c72011-04-01 14:43:32 -0700295 //
296 // IFullBackupRestoreObserver implementation
297 //
298 @Override
299 public void onStartBackup() throws RemoteException {
300 mHandler.sendEmptyMessage(MSG_START_BACKUP);
301 }
302
303 @Override
304 public void onBackupPackage(String name) throws RemoteException {
305 mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
306 }
307
308 @Override
309 public void onEndBackup() throws RemoteException {
310 mHandler.sendEmptyMessage(MSG_END_BACKUP);
311 }
312
313 @Override
314 public void onStartRestore() throws RemoteException {
315 mHandler.sendEmptyMessage(MSG_START_RESTORE);
316 }
317
318 @Override
319 public void onRestorePackage(String name) throws RemoteException {
320 mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
321 }
322
323 @Override
324 public void onEndRestore() throws RemoteException {
325 mHandler.sendEmptyMessage(MSG_END_RESTORE);
326 }
327
328 @Override
329 public void onTimeout() throws RemoteException {
330 mHandler.sendEmptyMessage(MSG_TIMEOUT);
331 }
332 }
333}