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