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