/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.backupconfirm; import android.app.Activity; import android.app.backup.FullBackup; import android.app.backup.IBackupManager; import android.app.backup.IFullBackupRestoreObserver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.text.Editable; import android.text.TextWatcher; import android.util.Slog; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; /** * Confirm with the user that a requested full backup/restore operation is legitimate. * Any attempt to perform a full backup/restore will launch this UI and wait for a * designated timeout interval (nominally 30 seconds) for the user to confirm. If the * user fails to respond within the timeout period, or explicitly refuses the operation * within the UI presented here, no data will be transferred off the device. * * Note that the fully scoped name of this class is baked into the backup manager service. * * @hide */ public class BackupRestoreConfirmation extends Activity { static final String TAG = "BackupRestoreConfirmation"; static final boolean DEBUG = true; static final String DID_ACKNOWLEDGE = "did_acknowledge"; static final int MSG_START_BACKUP = 1; static final int MSG_BACKUP_PACKAGE = 2; static final int MSG_END_BACKUP = 3; static final int MSG_START_RESTORE = 11; static final int MSG_RESTORE_PACKAGE = 12; static final int MSG_END_RESTORE = 13; static final int MSG_TIMEOUT = 100; Handler mHandler; IBackupManager mBackupManager; IMountService mMountService; FullObserver mObserver; int mToken; boolean mIsEncrypted; boolean mDidAcknowledge; TextView mStatusView; TextView mCurPassword; TextView mEncPassword; Button mAllowButton; Button mDenyButton; // Handler for dealing with observer callbacks on the main thread class ObserverHandler extends Handler { Context mContext; ObserverHandler(Context context) { mContext = context; mDidAcknowledge = false; } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_START_BACKUP: { Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show(); } break; case MSG_BACKUP_PACKAGE: { String name = (String) msg.obj; mStatusView.setText(name); } break; case MSG_END_BACKUP: { Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show(); finish(); } break; case MSG_START_RESTORE: { Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show(); } break; case MSG_RESTORE_PACKAGE: { String name = (String) msg.obj; mStatusView.setText(name); } break; case MSG_END_RESTORE: { Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show(); finish(); } break; case MSG_TIMEOUT: { Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show(); } break; } } } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); final Intent intent = getIntent(); final String action = intent.getAction(); final int layoutId; final int titleId; if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) { layoutId = R.layout.confirm_backup; titleId = R.string.backup_confirm_title; } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) { layoutId = R.layout.confirm_restore; titleId = R.string.restore_confirm_title; } else { Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!"); finish(); return; } mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1); if (mToken < 0) { Slog.e(TAG, "Backup/restore confirmation requested but no token passed!"); finish(); return; } mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE)); mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); mHandler = new ObserverHandler(getApplicationContext()); final Object oldObserver = getLastNonConfigurationInstance(); if (oldObserver == null) { mObserver = new FullObserver(mHandler); } else { mObserver = (FullObserver) oldObserver; mObserver.setHandler(mHandler); } setTitle(titleId); setContentView(layoutId); // Same resource IDs for each layout variant (backup / restore) mStatusView = (TextView) findViewById(R.id.package_name); mAllowButton = (Button) findViewById(R.id.button_allow); mDenyButton = (Button) findViewById(R.id.button_deny); mCurPassword = (TextView) findViewById(R.id.password); mEncPassword = (TextView) findViewById(R.id.enc_password); TextView curPwDesc = (TextView) findViewById(R.id.password_desc); mAllowButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendAcknowledgement(mToken, true, mObserver); mAllowButton.setEnabled(false); mDenyButton.setEnabled(false); } }); mDenyButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendAcknowledgement(mToken, false, mObserver); mAllowButton.setEnabled(false); mDenyButton.setEnabled(false); finish(); } }); // if we're a relaunch we may need to adjust button enable state if (icicle != null) { mDidAcknowledge = icicle.getBoolean(DID_ACKNOWLEDGE, false); mAllowButton.setEnabled(!mDidAcknowledge); mDenyButton.setEnabled(!mDidAcknowledge); } // We vary the password prompt depending on whether one is predefined, and whether // the device is encrypted. mIsEncrypted = deviceIsEncrypted(); if (!haveBackupPassword()) { curPwDesc.setVisibility(View.GONE); mCurPassword.setVisibility(View.GONE); if (layoutId == R.layout.confirm_backup) { TextView encPwDesc = (TextView) findViewById(R.id.enc_password_desc); if (mIsEncrypted) { encPwDesc.setText(R.string.backup_enc_password_required); monitorEncryptionPassword(); } else { encPwDesc.setText(R.string.backup_enc_password_optional); } } } } private void monitorEncryptionPassword() { mAllowButton.setEnabled(false); mEncPassword.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { mAllowButton.setEnabled(mEncPassword.getText().length() > 0); } }); } // Preserve the restore observer callback binder across activity relaunch @Override public Object onRetainNonConfigurationInstance() { return mObserver; } @Override protected void onSaveInstanceState(Bundle outState) { outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge); } void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) { if (!mDidAcknowledge) { mDidAcknowledge = true; try { CharSequence encPassword = mEncPassword.getText(); mBackupManager.acknowledgeFullBackupOrRestore(mToken, allow, String.valueOf(mCurPassword.getText()), String.valueOf(encPassword), mObserver); } catch (RemoteException e) { // TODO: bail gracefully if we can't contact the backup manager } } } boolean deviceIsEncrypted() { try { return mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE && mMountService.getPasswordType() != StorageManager.CRYPT_TYPE_DEFAULT; } catch (Exception e) { // If we can't talk to the mount service we have a serious problem; fail // "secure" i.e. assuming that the device is encrypted. Slog.e(TAG, "Unable to communicate with mount service: " + e.getMessage()); return true; } } boolean haveBackupPassword() { try { return mBackupManager.hasBackupPassword(); } catch (RemoteException e) { return true; // in the failure case, assume we need one } } /** * The observer binder for showing backup/restore progress. This binder just bounces * the notifications onto the main thread. */ class FullObserver extends IFullBackupRestoreObserver.Stub { private Handler mHandler; public FullObserver(Handler h) { mHandler = h; } public void setHandler(Handler h) { mHandler = h; } // // IFullBackupRestoreObserver implementation // @Override public void onStartBackup() throws RemoteException { mHandler.sendEmptyMessage(MSG_START_BACKUP); } @Override public void onBackupPackage(String name) throws RemoteException { mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name)); } @Override public void onEndBackup() throws RemoteException { mHandler.sendEmptyMessage(MSG_END_BACKUP); } @Override public void onStartRestore() throws RemoteException { mHandler.sendEmptyMessage(MSG_START_RESTORE); } @Override public void onRestorePackage(String name) throws RemoteException { mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name)); } @Override public void onEndRestore() throws RemoteException { mHandler.sendEmptyMessage(MSG_END_RESTORE); } @Override public void onTimeout() throws RemoteException { mHandler.sendEmptyMessage(MSG_TIMEOUT); } } }