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;
ObserverHandler(Context context)85         ObserverHandler(Context context) {
86             mContext = context;
87             mDidAcknowledge = false;
88         }
89 
90         @Override
handleMessage(Message msg)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
onCreate(Bundle icicle)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
onNewIntent(Intent intent)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 
setTokenOrFinish(Intent intent, Bundle icicle)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 
setViews(Intent intent, Bundle icicle)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 
monitorEncryptionPassword()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
onRetainNonConfigurationInstance()287     public Object onRetainNonConfigurationInstance() {
288         return mObserver;
289     }
290 
291     @Override
onSaveInstanceState(Bundle outState)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 
sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer)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 
deviceIsEncrypted()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 
haveBackupPassword()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 
FullObserver(Handler h)344         public FullObserver(Handler h) {
345             mHandler = h;
346         }
347 
setHandler(Handler h)348         public void setHandler(Handler h) {
349             mHandler = h;
350         }
351 
352         //
353         // IFullBackupRestoreObserver implementation
354         //
355         @Override
onStartBackup()356         public void onStartBackup() throws RemoteException {
357             mHandler.sendEmptyMessage(MSG_START_BACKUP);
358         }
359 
360         @Override
onBackupPackage(String name)361         public void onBackupPackage(String name) throws RemoteException {
362             mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
363         }
364 
365         @Override
onEndBackup()366         public void onEndBackup() throws RemoteException {
367             mHandler.sendEmptyMessage(MSG_END_BACKUP);
368         }
369 
370         @Override
onStartRestore()371         public void onStartRestore() throws RemoteException {
372             mHandler.sendEmptyMessage(MSG_START_RESTORE);
373         }
374 
375         @Override
onRestorePackage(String name)376         public void onRestorePackage(String name) throws RemoteException {
377             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
378         }
379 
380         @Override
onEndRestore()381         public void onEndRestore() throws RemoteException {
382             mHandler.sendEmptyMessage(MSG_END_RESTORE);
383         }
384 
385         @Override
onTimeout()386         public void onTimeout() throws RemoteException {
387             mHandler.sendEmptyMessage(MSG_TIMEOUT);
388         }
389     }
390 }
391