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.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