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.IMountService; 31 import android.util.Log; 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 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