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.text.Editable;
31 import android.text.TextWatcher;
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 KEY_DID_ACKNOWLEDGE = "did_acknowledge";
54     static final String KEY_TOKEN = "token";
55     static final String KEY_ACTION = "action";
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     FullObserver mObserver;
68     int mToken;
69     boolean mDidAcknowledge;
70     String mAction;
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;
ObserverHandler(Context context)81         ObserverHandler(Context context) {
82             mContext = context;
83             mDidAcknowledge = false;
84         }
85 
86         @Override
handleMessage(Message msg)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
onCreate(Bundle icicle)132     public void onCreate(Bundle icicle) {
133         super.onCreate(icicle);
134 
135         final Intent intent = getIntent();
136 
137         boolean tokenValid = setTokenOrFinish(intent, icicle);
138         if (!tokenValid) { // already called finish()
139             return;
140         }
141 
142         mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
143 
144         mHandler = new ObserverHandler(getApplicationContext());
145         final Object oldObserver = getLastNonConfigurationInstance();
146         if (oldObserver == null) {
147             mObserver = new FullObserver(mHandler);
148         } else {
149             mObserver = (FullObserver) oldObserver;
150             mObserver.setHandler(mHandler);
151         }
152 
153         setViews(intent, icicle);
154     }
155 
156     @Override
onNewIntent(Intent intent)157     public void onNewIntent(Intent intent) {
158         super.onNewIntent(intent);
159         setIntent(intent);
160 
161         boolean tokenValid = setTokenOrFinish(intent, null);
162         if (!tokenValid) { // already called finish()
163             return;
164         }
165 
166         setViews(intent, null);
167     }
168 
setTokenOrFinish(Intent intent, Bundle icicle)169     private boolean setTokenOrFinish(Intent intent, Bundle icicle) {
170         mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1);
171 
172         // for relaunch, we try to use the last token before exit
173         if (icicle != null) {
174             mToken = icicle.getInt(KEY_TOKEN, mToken);
175         }
176 
177         if (mToken < 0) {
178             Slog.e(TAG, "Backup/restore confirmation requested but no token passed!");
179             finish();
180             return false;
181         }
182 
183         return true;
184     }
185 
setViews(Intent intent, Bundle icicle)186     private void setViews(Intent intent, Bundle icicle) {
187         mAction = intent.getAction();
188 
189         // for relaunch, we try to use the last action before exit
190         if (icicle != null) {
191             mAction = icicle.getString(KEY_ACTION, mAction);
192         }
193 
194         final int layoutId;
195         final int titleId;
196         if (mAction.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) {
197             layoutId = R.layout.confirm_backup;
198             titleId = R.string.backup_confirm_title;
199         } else if (mAction.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) {
200             layoutId = R.layout.confirm_restore;
201             titleId = R.string.restore_confirm_title;
202         } else {
203             Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!");
204             finish();
205             return;
206         }
207 
208         setTitle(titleId);
209         setContentView(layoutId);
210 
211         // Same resource IDs for each layout variant (backup / restore)
212         mStatusView = findViewById(R.id.package_name);
213         mAllowButton = findViewById(R.id.button_allow);
214         mDenyButton = findViewById(R.id.button_deny);
215 
216         mCurPassword = findViewById(R.id.password);
217         mEncPassword = findViewById(R.id.enc_password);
218         TextView curPwDesc = findViewById(R.id.password_desc);
219 
220         mAllowButton.setOnClickListener(new View.OnClickListener() {
221             @Override
222             public void onClick(View v) {
223                 sendAcknowledgement(mToken, true, mObserver);
224                 mAllowButton.setEnabled(false);
225                 mDenyButton.setEnabled(false);
226             }
227         });
228 
229         mDenyButton.setOnClickListener(new View.OnClickListener() {
230             @Override
231             public void onClick(View v) {
232                 sendAcknowledgement(mToken, false, mObserver);
233                 mAllowButton.setEnabled(false);
234                 mDenyButton.setEnabled(false);
235                 finish();
236             }
237         });
238 
239         // if we're a relaunch we may need to adjust button enable state
240         if (icicle != null) {
241             mDidAcknowledge = icicle.getBoolean(KEY_DID_ACKNOWLEDGE, false);
242             mAllowButton.setEnabled(!mDidAcknowledge);
243             mDenyButton.setEnabled(!mDidAcknowledge);
244         }
245 
246         // We vary the password prompt depending on whether one is predefined.
247         if (!haveBackupPassword()) {
248             curPwDesc.setVisibility(View.GONE);
249             mCurPassword.setVisibility(View.GONE);
250             if (layoutId == R.layout.confirm_backup) {
251                 TextView encPwDesc = findViewById(R.id.enc_password_desc);
252                 encPwDesc.setText(R.string.backup_enc_password_optional);
253             }
254         }
255     }
256 
monitorEncryptionPassword()257     private void monitorEncryptionPassword() {
258         mAllowButton.setEnabled(false);
259         mEncPassword.addTextChangedListener(new TextWatcher() {
260             @Override
261             public void onTextChanged(CharSequence s, int start, int before, int count) { }
262 
263             @Override
264             public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
265 
266             @Override
267             public void afterTextChanged(Editable s) {
268                 mAllowButton.setEnabled(mEncPassword.getText().length() > 0);
269             }
270         });
271     }
272 
273     // Preserve the restore observer callback binder across activity relaunch
274     @Override
onRetainNonConfigurationInstance()275     public Object onRetainNonConfigurationInstance() {
276         return mObserver;
277     }
278 
279     @Override
onSaveInstanceState(Bundle outState)280     protected void onSaveInstanceState(Bundle outState) {
281         outState.putBoolean(KEY_DID_ACKNOWLEDGE, mDidAcknowledge);
282         outState.putInt(KEY_TOKEN, mToken);
283         outState.putString(KEY_ACTION, mAction);
284     }
285 
sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer)286     void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) {
287         if (!mDidAcknowledge) {
288             mDidAcknowledge = true;
289 
290             try {
291                 CharSequence encPassword = mEncPassword.getText();
292                 mBackupManager.acknowledgeFullBackupOrRestore(mToken,
293                         allow,
294                         String.valueOf(mCurPassword.getText()),
295                         String.valueOf(encPassword),
296                         mObserver);
297             } catch (RemoteException e) {
298                 // TODO: bail gracefully if we can't contact the backup manager
299             }
300         }
301     }
302 
haveBackupPassword()303     boolean haveBackupPassword() {
304         try {
305             return mBackupManager.hasBackupPassword();
306         } catch (RemoteException e) {
307             return true;        // in the failure case, assume we need one
308         }
309     }
310 
311     /**
312      * The observer binder for showing backup/restore progress.  This binder just bounces
313      * the notifications onto the main thread.
314      */
315     class FullObserver extends IFullBackupRestoreObserver.Stub {
316         private Handler mHandler;
317 
FullObserver(Handler h)318         public FullObserver(Handler h) {
319             mHandler = h;
320         }
321 
setHandler(Handler h)322         public void setHandler(Handler h) {
323             mHandler = h;
324         }
325 
326         //
327         // IFullBackupRestoreObserver implementation
328         //
329         @Override
onStartBackup()330         public void onStartBackup() throws RemoteException {
331             mHandler.sendEmptyMessage(MSG_START_BACKUP);
332         }
333 
334         @Override
onBackupPackage(String name)335         public void onBackupPackage(String name) throws RemoteException {
336             mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
337         }
338 
339         @Override
onEndBackup()340         public void onEndBackup() throws RemoteException {
341             mHandler.sendEmptyMessage(MSG_END_BACKUP);
342         }
343 
344         @Override
onStartRestore()345         public void onStartRestore() throws RemoteException {
346             mHandler.sendEmptyMessage(MSG_START_RESTORE);
347         }
348 
349         @Override
onRestorePackage(String name)350         public void onRestorePackage(String name) throws RemoteException {
351             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
352         }
353 
354         @Override
onEndRestore()355         public void onEndRestore() throws RemoteException {
356             mHandler.sendEmptyMessage(MSG_END_RESTORE);
357         }
358 
359         @Override
onTimeout()360         public void onTimeout() throws RemoteException {
361             mHandler.sendEmptyMessage(MSG_TIMEOUT);
362         }
363     }
364 }
365