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