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.text.Editable; 31 import android.text.TextWatcher; 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 KEY_DID_ACKNOWLEDGE = "did_acknowledge"; 54 static final String KEY_TOKEN = "token"; 55 static final String KEY_ACTION = "action"; 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 FullObserver mObserver; 68 int mToken; 69 boolean mDidAcknowledge; 70 String mAction; 71 72 TextView mStatusView; 73 TextView mCurPassword; 74 TextView mEncPassword; 75 Button mAllowButton; 76 Button mDenyButton; 77 78 // Handler for dealing with observer callbacks on the main thread 79 class ObserverHandler extends Handler { 80 Context mContext; ObserverHandler(Context context)81 ObserverHandler(Context context) { 82 mContext = context; 83 mDidAcknowledge = false; 84 } 85 86 @Override handleMessage(Message msg)87 public void handleMessage(Message msg) { 88 switch (msg.what) { 89 case MSG_START_BACKUP: { 90 Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show(); 91 } 92 break; 93 94 case MSG_BACKUP_PACKAGE: { 95 String name = (String) msg.obj; 96 mStatusView.setText(name); 97 } 98 break; 99 100 case MSG_END_BACKUP: { 101 Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show(); 102 finish(); 103 } 104 break; 105 106 case MSG_START_RESTORE: { 107 Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show(); 108 } 109 break; 110 111 case MSG_RESTORE_PACKAGE: { 112 String name = (String) msg.obj; 113 mStatusView.setText(name); 114 } 115 break; 116 117 case MSG_END_RESTORE: { 118 Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show(); 119 finish(); 120 } 121 break; 122 123 case MSG_TIMEOUT: { 124 Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show(); 125 } 126 break; 127 } 128 } 129 } 130 131 @Override onCreate(Bundle icicle)132 public void onCreate(Bundle icicle) { 133 super.onCreate(icicle); 134 135 final Intent intent = getIntent(); 136 137 boolean tokenValid = setTokenOrFinish(intent, icicle); 138 if (!tokenValid) { // already called finish() 139 return; 140 } 141 142 mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE)); 143 144 mHandler = new ObserverHandler(getApplicationContext()); 145 final Object oldObserver = getLastNonConfigurationInstance(); 146 if (oldObserver == null) { 147 mObserver = new FullObserver(mHandler); 148 } else { 149 mObserver = (FullObserver) oldObserver; 150 mObserver.setHandler(mHandler); 151 } 152 153 setViews(intent, icicle); 154 } 155 156 @Override onNewIntent(Intent intent)157 public void onNewIntent(Intent intent) { 158 super.onNewIntent(intent); 159 setIntent(intent); 160 161 boolean tokenValid = setTokenOrFinish(intent, null); 162 if (!tokenValid) { // already called finish() 163 return; 164 } 165 166 setViews(intent, null); 167 } 168 setTokenOrFinish(Intent intent, Bundle icicle)169 private boolean setTokenOrFinish(Intent intent, Bundle icicle) { 170 mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1); 171 172 // for relaunch, we try to use the last token before exit 173 if (icicle != null) { 174 mToken = icicle.getInt(KEY_TOKEN, mToken); 175 } 176 177 if (mToken < 0) { 178 Slog.e(TAG, "Backup/restore confirmation requested but no token passed!"); 179 finish(); 180 return false; 181 } 182 183 return true; 184 } 185 setViews(Intent intent, Bundle icicle)186 private void setViews(Intent intent, Bundle icicle) { 187 mAction = intent.getAction(); 188 189 // for relaunch, we try to use the last action before exit 190 if (icicle != null) { 191 mAction = icicle.getString(KEY_ACTION, mAction); 192 } 193 194 final int layoutId; 195 final int titleId; 196 if (mAction.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) { 197 layoutId = R.layout.confirm_backup; 198 titleId = R.string.backup_confirm_title; 199 } else if (mAction.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) { 200 layoutId = R.layout.confirm_restore; 201 titleId = R.string.restore_confirm_title; 202 } else { 203 Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!"); 204 finish(); 205 return; 206 } 207 208 setTitle(titleId); 209 setContentView(layoutId); 210 211 // Same resource IDs for each layout variant (backup / restore) 212 mStatusView = findViewById(R.id.package_name); 213 mAllowButton = findViewById(R.id.button_allow); 214 mDenyButton = findViewById(R.id.button_deny); 215 216 mCurPassword = findViewById(R.id.password); 217 mEncPassword = findViewById(R.id.enc_password); 218 TextView curPwDesc = findViewById(R.id.password_desc); 219 220 mAllowButton.setOnClickListener(new View.OnClickListener() { 221 @Override 222 public void onClick(View v) { 223 sendAcknowledgement(mToken, true, mObserver); 224 mAllowButton.setEnabled(false); 225 mDenyButton.setEnabled(false); 226 } 227 }); 228 229 mDenyButton.setOnClickListener(new View.OnClickListener() { 230 @Override 231 public void onClick(View v) { 232 sendAcknowledgement(mToken, false, mObserver); 233 mAllowButton.setEnabled(false); 234 mDenyButton.setEnabled(false); 235 finish(); 236 } 237 }); 238 239 // if we're a relaunch we may need to adjust button enable state 240 if (icicle != null) { 241 mDidAcknowledge = icicle.getBoolean(KEY_DID_ACKNOWLEDGE, false); 242 mAllowButton.setEnabled(!mDidAcknowledge); 243 mDenyButton.setEnabled(!mDidAcknowledge); 244 } 245 246 // We vary the password prompt depending on whether one is predefined. 247 if (!haveBackupPassword()) { 248 curPwDesc.setVisibility(View.GONE); 249 mCurPassword.setVisibility(View.GONE); 250 if (layoutId == R.layout.confirm_backup) { 251 TextView encPwDesc = findViewById(R.id.enc_password_desc); 252 encPwDesc.setText(R.string.backup_enc_password_optional); 253 } 254 } 255 } 256 monitorEncryptionPassword()257 private void monitorEncryptionPassword() { 258 mAllowButton.setEnabled(false); 259 mEncPassword.addTextChangedListener(new TextWatcher() { 260 @Override 261 public void onTextChanged(CharSequence s, int start, int before, int count) { } 262 263 @Override 264 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 265 266 @Override 267 public void afterTextChanged(Editable s) { 268 mAllowButton.setEnabled(mEncPassword.getText().length() > 0); 269 } 270 }); 271 } 272 273 // Preserve the restore observer callback binder across activity relaunch 274 @Override onRetainNonConfigurationInstance()275 public Object onRetainNonConfigurationInstance() { 276 return mObserver; 277 } 278 279 @Override onSaveInstanceState(Bundle outState)280 protected void onSaveInstanceState(Bundle outState) { 281 outState.putBoolean(KEY_DID_ACKNOWLEDGE, mDidAcknowledge); 282 outState.putInt(KEY_TOKEN, mToken); 283 outState.putString(KEY_ACTION, mAction); 284 } 285 sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer)286 void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) { 287 if (!mDidAcknowledge) { 288 mDidAcknowledge = true; 289 290 try { 291 CharSequence encPassword = mEncPassword.getText(); 292 mBackupManager.acknowledgeFullBackupOrRestore(mToken, 293 allow, 294 String.valueOf(mCurPassword.getText()), 295 String.valueOf(encPassword), 296 mObserver); 297 } catch (RemoteException e) { 298 // TODO: bail gracefully if we can't contact the backup manager 299 } 300 } 301 } 302 haveBackupPassword()303 boolean haveBackupPassword() { 304 try { 305 return mBackupManager.hasBackupPassword(); 306 } catch (RemoteException e) { 307 return true; // in the failure case, assume we need one 308 } 309 } 310 311 /** 312 * The observer binder for showing backup/restore progress. This binder just bounces 313 * the notifications onto the main thread. 314 */ 315 class FullObserver extends IFullBackupRestoreObserver.Stub { 316 private Handler mHandler; 317 FullObserver(Handler h)318 public FullObserver(Handler h) { 319 mHandler = h; 320 } 321 setHandler(Handler h)322 public void setHandler(Handler h) { 323 mHandler = h; 324 } 325 326 // 327 // IFullBackupRestoreObserver implementation 328 // 329 @Override onStartBackup()330 public void onStartBackup() throws RemoteException { 331 mHandler.sendEmptyMessage(MSG_START_BACKUP); 332 } 333 334 @Override onBackupPackage(String name)335 public void onBackupPackage(String name) throws RemoteException { 336 mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name)); 337 } 338 339 @Override onEndBackup()340 public void onEndBackup() throws RemoteException { 341 mHandler.sendEmptyMessage(MSG_END_BACKUP); 342 } 343 344 @Override onStartRestore()345 public void onStartRestore() throws RemoteException { 346 mHandler.sendEmptyMessage(MSG_START_RESTORE); 347 } 348 349 @Override onRestorePackage(String name)350 public void onRestorePackage(String name) throws RemoteException { 351 mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name)); 352 } 353 354 @Override onEndRestore()355 public void onEndRestore() throws RemoteException { 356 mHandler.sendEmptyMessage(MSG_END_RESTORE); 357 } 358 359 @Override onTimeout()360 public void onTimeout() throws RemoteException { 361 mHandler.sendEmptyMessage(MSG_TIMEOUT); 362 } 363 } 364 } 365