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