Home | History | Annotate | Download | only in backupconfirm
      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.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     IMountService mMountService;
     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;
     82         ObserverHandler(Context context) {
     83             mContext = context;
     84             mDidAcknowledge = false;
     85         }
     86 
     87         @Override
     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
    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         mMountService = IMountService.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 = (TextView) findViewById(R.id.package_name);
    177         mAllowButton = (Button) findViewById(R.id.button_allow);
    178         mDenyButton = (Button) findViewById(R.id.button_deny);
    179 
    180         mCurPassword = (TextView) findViewById(R.id.password);
    181         mEncPassword = (TextView) findViewById(R.id.enc_password);
    182         TextView curPwDesc = (TextView) 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 = (TextView) 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 
    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
    246     public Object onRetainNonConfigurationInstance() {
    247         return mObserver;
    248     }
    249 
    250     @Override
    251     protected void onSaveInstanceState(Bundle outState) {
    252         outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge);
    253     }
    254 
    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 
    272     boolean deviceIsEncrypted() {
    273         try {
    274             return mMountService.getEncryptionState()
    275                      != IMountService.ENCRYPTION_STATE_NONE
    276                 && mMountService.getPasswordType()
    277                      != StorageManager.CRYPT_TYPE_DEFAULT;
    278         } catch (Exception e) {
    279             // If we can't talk to the mount 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 mount service: " + e.getMessage());
    282             return true;
    283         }
    284     }
    285 
    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 
    301         public FullObserver(Handler h) {
    302             mHandler = h;
    303         }
    304 
    305         public void setHandler(Handler h) {
    306             mHandler = h;
    307         }
    308 
    309         //
    310         // IFullBackupRestoreObserver implementation
    311         //
    312         @Override
    313         public void onStartBackup() throws RemoteException {
    314             mHandler.sendEmptyMessage(MSG_START_BACKUP);
    315         }
    316 
    317         @Override
    318         public void onBackupPackage(String name) throws RemoteException {
    319             mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
    320         }
    321 
    322         @Override
    323         public void onEndBackup() throws RemoteException {
    324             mHandler.sendEmptyMessage(MSG_END_BACKUP);
    325         }
    326 
    327         @Override
    328         public void onStartRestore() throws RemoteException {
    329             mHandler.sendEmptyMessage(MSG_START_RESTORE);
    330         }
    331 
    332         @Override
    333         public void onRestorePackage(String name) throws RemoteException {
    334             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
    335         }
    336 
    337         @Override
    338         public void onEndRestore() throws RemoteException {
    339             mHandler.sendEmptyMessage(MSG_END_RESTORE);
    340         }
    341 
    342         @Override
    343         public void onTimeout() throws RemoteException {
    344             mHandler.sendEmptyMessage(MSG_TIMEOUT);
    345         }
    346     }
    347 }
    348