Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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 | |
| 17 | package com.android.backupconfirm; |
| 18 | |
| 19 | import android.app.Activity; |
| 20 | import android.app.backup.FullBackup; |
| 21 | import android.app.backup.IBackupManager; |
| 22 | import android.app.backup.IFullBackupRestoreObserver; |
| 23 | import android.content.Context; |
| 24 | import android.content.Intent; |
| 25 | import android.os.Bundle; |
| 26 | import android.os.Handler; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 27 | import android.os.Message; |
| 28 | import android.os.RemoteException; |
| 29 | import android.os.ServiceManager; |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 30 | import android.os.storage.IMountService; |
| 31 | import android.util.Log; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 32 | import android.util.Slog; |
| 33 | import android.view.View; |
| 34 | import android.widget.Button; |
| 35 | import android.widget.TextView; |
| 36 | import 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 | */ |
| 49 | public class BackupRestoreConfirmation extends Activity { |
| 50 | static final String TAG = "BackupRestoreConfirmation"; |
| 51 | static final boolean DEBUG = true; |
| 52 | |
Christopher Tate | 3851fa9 | 2011-08-05 12:28:15 -0700 | [diff] [blame] | 53 | static final String DID_ACKNOWLEDGE = "did_acknowledge"; |
| 54 | |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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; |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 65 | IMountService mMountService; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 66 | FullObserver mObserver; |
| 67 | int mToken; |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 68 | boolean mIsEncrypted; |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 69 | boolean mDidAcknowledge; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 70 | |
| 71 | TextView mStatusView; |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 72 | TextView mCurPassword; |
| 73 | TextView mEncPassword; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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; |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 82 | mDidAcknowledge = false; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 83 | } |
| 84 | |
| 85 | @Override |
| 86 | public void handleMessage(Message msg) { |
| 87 | switch (msg.what) { |
| 88 | case MSG_START_BACKUP: { |
Christopher Tate | 28b591c | 2011-09-14 17:34:52 -0700 | [diff] [blame] | 89 | Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show(); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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: { |
Christopher Tate | 28b591c | 2011-09-14 17:34:52 -0700 | [diff] [blame] | 100 | Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show(); |
Christopher Tate | dc92c82 | 2011-05-13 15:38:02 -0700 | [diff] [blame] | 101 | finish(); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 102 | } |
| 103 | break; |
| 104 | |
| 105 | case MSG_START_RESTORE: { |
Christopher Tate | 28b591c | 2011-09-14 17:34:52 -0700 | [diff] [blame] | 106 | Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show(); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 107 | } |
| 108 | break; |
| 109 | |
| 110 | case MSG_RESTORE_PACKAGE: { |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 111 | String name = (String) msg.obj; |
| 112 | mStatusView.setText(name); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 113 | } |
| 114 | break; |
| 115 | |
| 116 | case MSG_END_RESTORE: { |
Christopher Tate | 28b591c | 2011-09-14 17:34:52 -0700 | [diff] [blame] | 117 | Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show(); |
Christopher Tate | dc92c82 | 2011-05-13 15:38:02 -0700 | [diff] [blame] | 118 | finish(); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 119 | } |
| 120 | break; |
| 121 | |
| 122 | case MSG_TIMEOUT: { |
Christopher Tate | 28b591c | 2011-09-14 17:34:52 -0700 | [diff] [blame] | 123 | Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show(); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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 | |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 137 | final int layoutId; |
Christopher Tate | c58cf7d | 2011-09-13 17:51:18 -0700 | [diff] [blame] | 138 | final int titleId; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 139 | if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) { |
| 140 | layoutId = R.layout.confirm_backup; |
Christopher Tate | c58cf7d | 2011-09-13 17:51:18 -0700 | [diff] [blame] | 141 | titleId = R.string.backup_confirm_title; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 142 | } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) { |
| 143 | layoutId = R.layout.confirm_restore; |
Christopher Tate | c58cf7d | 2011-09-13 17:51:18 -0700 | [diff] [blame] | 144 | titleId = R.string.restore_confirm_title; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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)); |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 159 | mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 160 | |
| 161 | mHandler = new ObserverHandler(getApplicationContext()); |
Christopher Tate | 3851fa9 | 2011-08-05 12:28:15 -0700 | [diff] [blame] | 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 | } |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 169 | |
Christopher Tate | c58cf7d | 2011-09-13 17:51:18 -0700 | [diff] [blame] | 170 | setTitle(titleId); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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 | |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 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 | |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 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()) { |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 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); |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 204 | } |
| 205 | } |
| 206 | |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 207 | mAllowButton.setOnClickListener(new View.OnClickListener() { |
| 208 | @Override |
| 209 | public void onClick(View v) { |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 210 | sendAcknowledgement(mToken, true, mObserver); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 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) { |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 219 | sendAcknowledgement(mToken, false, mObserver); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 220 | mAllowButton.setEnabled(false); |
| 221 | mDenyButton.setEnabled(false); |
| 222 | } |
| 223 | }); |
Christopher Tate | 3851fa9 | 2011-08-05 12:28:15 -0700 | [diff] [blame] | 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; |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | @Override |
Christopher Tate | 3851fa9 | 2011-08-05 12:28:15 -0700 | [diff] [blame] | 240 | protected void onSaveInstanceState(Bundle outState) { |
| 241 | outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 242 | } |
| 243 | |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 244 | void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) { |
| 245 | if (!mDidAcknowledge) { |
| 246 | mDidAcknowledge = true; |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 247 | |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 248 | try { |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 249 | CharSequence encPassword = (mIsEncrypted) |
| 250 | ? mCurPassword.getText() : mEncPassword.getText(); |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 251 | mBackupManager.acknowledgeFullBackupOrRestore(mToken, |
| 252 | allow, |
| 253 | String.valueOf(mCurPassword.getText()), |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 254 | String.valueOf(encPassword), |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 255 | mObserver); |
Christopher Tate | ec5d4a0 | 2011-06-07 15:35:45 -0700 | [diff] [blame] | 256 | } catch (RemoteException e) { |
| 257 | // TODO: bail gracefully if we can't contact the backup manager |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
Christopher Tate | 32418be | 2011-10-10 13:51:12 -0700 | [diff] [blame^] | 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 | return true; |
| 269 | } |
| 270 | } |
| 271 | |
Christopher Tate | 728a1c4 | 2011-07-28 18:03:03 -0700 | [diff] [blame] | 272 | 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 Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 280 | /** |
| 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 Tate | 3851fa9 | 2011-08-05 12:28:15 -0700 | [diff] [blame] | 285 | 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 Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 295 | // |
| 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 | } |