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