• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.view.ViewGroup.MarginLayoutParams;
35 import android.widget.Button;
36 import android.widget.LinearLayout;
37 import android.widget.ScrollView;
38 import android.widget.TextView;
39 import android.widget.Toast;
40 
41 import androidx.core.graphics.Insets;
42 import androidx.core.view.ViewCompat;
43 import androidx.core.view.WindowInsetsCompat;
44 
45 /**
46  * Confirm with the user that a requested full backup/restore operation is legitimate.
47  * Any attempt to perform a full backup/restore will launch this UI and wait for a
48  * designated timeout interval (nominally 30 seconds) for the user to confirm.  If the
49  * user fails to respond within the timeout period, or explicitly refuses the operation
50  * within the UI presented here, no data will be transferred off the device.
51  *
52  * Note that the fully scoped name of this class is baked into the backup manager service.
53  *
54  * @hide
55  */
56 public class BackupRestoreConfirmation extends Activity {
57     static final String TAG = "BackupRestoreConfirmation";
58     static final boolean DEBUG = true;
59 
60     static final String KEY_DID_ACKNOWLEDGE = "did_acknowledge";
61     static final String KEY_TOKEN = "token";
62     static final String KEY_ACTION = "action";
63 
64     static final int MSG_START_BACKUP = 1;
65     static final int MSG_BACKUP_PACKAGE = 2;
66     static final int MSG_END_BACKUP = 3;
67     static final int MSG_START_RESTORE = 11;
68     static final int MSG_RESTORE_PACKAGE = 12;
69     static final int MSG_END_RESTORE = 13;
70     static final int MSG_TIMEOUT = 100;
71 
72     Handler mHandler;
73     IBackupManager mBackupManager;
74     FullObserver mObserver;
75     int mToken;
76     boolean mDidAcknowledge;
77     String mAction;
78 
79     TextView mStatusView;
80     TextView mCurPassword;
81     TextView mEncPassword;
82     Button mAllowButton;
83     Button mDenyButton;
84 
85     // Handler for dealing with observer callbacks on the main thread
86     class ObserverHandler extends Handler {
87         Context mContext;
ObserverHandler(Context context)88         ObserverHandler(Context context) {
89             mContext = context;
90             mDidAcknowledge = false;
91         }
92 
93         @Override
handleMessage(Message msg)94         public void handleMessage(Message msg) {
95             switch (msg.what) {
96                 case MSG_START_BACKUP: {
97                     Toast.makeText(mContext, R.string.toast_backup_started, Toast.LENGTH_LONG).show();
98                 }
99                 break;
100 
101                 case MSG_BACKUP_PACKAGE: {
102                     String name = (String) msg.obj;
103                     mStatusView.setText(name);
104                 }
105                 break;
106 
107                 case MSG_END_BACKUP: {
108                     Toast.makeText(mContext, R.string.toast_backup_ended, Toast.LENGTH_LONG).show();
109                     finish();
110                 }
111                 break;
112 
113                 case MSG_START_RESTORE: {
114                     Toast.makeText(mContext, R.string.toast_restore_started, Toast.LENGTH_LONG).show();
115                 }
116                 break;
117 
118                 case MSG_RESTORE_PACKAGE: {
119                     String name = (String) msg.obj;
120                     mStatusView.setText(name);
121                 }
122                 break;
123 
124                 case MSG_END_RESTORE: {
125                     Toast.makeText(mContext, R.string.toast_restore_ended, Toast.LENGTH_SHORT).show();
126                     finish();
127                 }
128                 break;
129 
130                 case MSG_TIMEOUT: {
131                     Toast.makeText(mContext, R.string.toast_timeout, Toast.LENGTH_LONG).show();
132                 }
133                 break;
134             }
135         }
136     }
137 
138     @Override
onCreate(Bundle icicle)139     public void onCreate(Bundle icicle) {
140         super.onCreate(icicle);
141 
142         final Intent intent = getIntent();
143 
144         boolean tokenValid = setTokenOrFinish(intent, icicle);
145         if (!tokenValid) { // already called finish()
146             return;
147         }
148 
149         mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
150 
151         mHandler = new ObserverHandler(getApplicationContext());
152         final Object oldObserver = getLastNonConfigurationInstance();
153         if (oldObserver == null) {
154             mObserver = new FullObserver(mHandler);
155         } else {
156             mObserver = (FullObserver) oldObserver;
157             mObserver.setHandler(mHandler);
158         }
159 
160         setViews(intent, icicle);
161     }
162 
163     @Override
onNewIntent(Intent intent)164     public void onNewIntent(Intent intent) {
165         super.onNewIntent(intent);
166         setIntent(intent);
167 
168         boolean tokenValid = setTokenOrFinish(intent, null);
169         if (!tokenValid) { // already called finish()
170             return;
171         }
172 
173         setViews(intent, null);
174     }
175 
setTokenOrFinish(Intent intent, Bundle icicle)176     private boolean setTokenOrFinish(Intent intent, Bundle icicle) {
177         mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1);
178 
179         // for relaunch, we try to use the last token before exit
180         if (icicle != null) {
181             mToken = icicle.getInt(KEY_TOKEN, mToken);
182         }
183 
184         if (mToken < 0) {
185             Slog.e(TAG, "Backup/restore confirmation requested but no token passed!");
186             finish();
187             return false;
188         }
189 
190         return true;
191     }
192 
setViews(Intent intent, Bundle icicle)193     private void setViews(Intent intent, Bundle icicle) {
194         mAction = intent.getAction();
195 
196         // for relaunch, we try to use the last action before exit
197         if (icicle != null) {
198             mAction = icicle.getString(KEY_ACTION, mAction);
199         }
200 
201         final int layoutId;
202         final int titleId;
203         if (mAction.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) {
204             layoutId = R.layout.confirm_backup;
205             titleId = R.string.backup_confirm_title;
206         } else if (mAction.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) {
207             layoutId = R.layout.confirm_restore;
208             titleId = R.string.restore_confirm_title;
209         } else {
210             Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!");
211             finish();
212             return;
213         }
214 
215         setTitle(titleId);
216         setContentView(layoutId);
217 
218         handleInsets();
219 
220         // Same resource IDs for each layout variant (backup / restore)
221         mStatusView = findViewById(R.id.package_name);
222         mAllowButton = findViewById(R.id.button_allow);
223         mDenyButton = findViewById(R.id.button_deny);
224 
225         mCurPassword = findViewById(R.id.password);
226         mEncPassword = findViewById(R.id.enc_password);
227         TextView curPwDesc = findViewById(R.id.password_desc);
228 
229         mAllowButton.setOnClickListener(new View.OnClickListener() {
230             @Override
231             public void onClick(View v) {
232                 sendAcknowledgement(mToken, true, mObserver);
233                 mAllowButton.setEnabled(false);
234                 mDenyButton.setEnabled(false);
235             }
236         });
237 
238         mDenyButton.setOnClickListener(new View.OnClickListener() {
239             @Override
240             public void onClick(View v) {
241                 sendAcknowledgement(mToken, false, mObserver);
242                 mAllowButton.setEnabled(false);
243                 mDenyButton.setEnabled(false);
244                 finish();
245             }
246         });
247 
248         // if we're a relaunch we may need to adjust button enable state
249         if (icicle != null) {
250             mDidAcknowledge = icicle.getBoolean(KEY_DID_ACKNOWLEDGE, false);
251             mAllowButton.setEnabled(!mDidAcknowledge);
252             mDenyButton.setEnabled(!mDidAcknowledge);
253         }
254 
255         // We vary the password prompt depending on whether one is predefined.
256         if (!haveBackupPassword()) {
257             curPwDesc.setVisibility(View.GONE);
258             mCurPassword.setVisibility(View.GONE);
259             if (layoutId == R.layout.confirm_backup) {
260                 TextView encPwDesc = findViewById(R.id.enc_password_desc);
261                 encPwDesc.setText(R.string.backup_enc_password_optional);
262             }
263         }
264     }
265 
266     // Handle insets so that UI components are not covered by navigation and status bars
handleInsets()267     private void handleInsets() {
268         LinearLayout buttonBar = findViewById(R.id.button_bar);
269         ViewCompat.setOnApplyWindowInsetsListener(buttonBar, (v, windowInsets) -> {
270             Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
271             MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
272             mlp.leftMargin = insets.left;
273             mlp.bottomMargin = insets.bottom;
274             mlp.rightMargin = insets.right;
275             v.setLayoutParams(mlp);
276             return WindowInsetsCompat.CONSUMED;
277         });
278 
279         ScrollView scrollView = findViewById(R.id.scroll_view);
280         ViewCompat.setOnApplyWindowInsetsListener(scrollView, (v, windowInsets) -> {
281             Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
282             MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
283             mlp.leftMargin = insets.left;
284             mlp.topMargin = insets.top;
285             mlp.rightMargin = insets.right;
286             v.setLayoutParams(mlp);
287             return WindowInsetsCompat.CONSUMED;
288         });
289     }
290 
monitorEncryptionPassword()291     private void monitorEncryptionPassword() {
292         mAllowButton.setEnabled(false);
293         mEncPassword.addTextChangedListener(new TextWatcher() {
294             @Override
295             public void onTextChanged(CharSequence s, int start, int before, int count) { }
296 
297             @Override
298             public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
299 
300             @Override
301             public void afterTextChanged(Editable s) {
302                 mAllowButton.setEnabled(mEncPassword.getText().length() > 0);
303             }
304         });
305     }
306 
307     // Preserve the restore observer callback binder across activity relaunch
308     @Override
onRetainNonConfigurationInstance()309     public Object onRetainNonConfigurationInstance() {
310         return mObserver;
311     }
312 
313     @Override
onSaveInstanceState(Bundle outState)314     protected void onSaveInstanceState(Bundle outState) {
315         outState.putBoolean(KEY_DID_ACKNOWLEDGE, mDidAcknowledge);
316         outState.putInt(KEY_TOKEN, mToken);
317         outState.putString(KEY_ACTION, mAction);
318     }
319 
sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer)320     void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) {
321         if (!mDidAcknowledge) {
322             mDidAcknowledge = true;
323 
324             try {
325                 CharSequence encPassword = mEncPassword.getText();
326                 mBackupManager.acknowledgeFullBackupOrRestore(mToken,
327                         allow,
328                         String.valueOf(mCurPassword.getText()),
329                         String.valueOf(encPassword),
330                         mObserver);
331             } catch (RemoteException e) {
332                 // TODO: bail gracefully if we can't contact the backup manager
333             }
334         }
335     }
336 
haveBackupPassword()337     boolean haveBackupPassword() {
338         try {
339             return mBackupManager.hasBackupPassword();
340         } catch (RemoteException e) {
341             return true;        // in the failure case, assume we need one
342         }
343     }
344 
345     /**
346      * The observer binder for showing backup/restore progress.  This binder just bounces
347      * the notifications onto the main thread.
348      */
349     class FullObserver extends IFullBackupRestoreObserver.Stub {
350         private Handler mHandler;
351 
FullObserver(Handler h)352         public FullObserver(Handler h) {
353             mHandler = h;
354         }
355 
setHandler(Handler h)356         public void setHandler(Handler h) {
357             mHandler = h;
358         }
359 
360         //
361         // IFullBackupRestoreObserver implementation
362         //
363         @Override
onStartBackup()364         public void onStartBackup() throws RemoteException {
365             mHandler.sendEmptyMessage(MSG_START_BACKUP);
366         }
367 
368         @Override
onBackupPackage(String name)369         public void onBackupPackage(String name) throws RemoteException {
370             mHandler.sendMessage(mHandler.obtainMessage(MSG_BACKUP_PACKAGE, name));
371         }
372 
373         @Override
onEndBackup()374         public void onEndBackup() throws RemoteException {
375             mHandler.sendEmptyMessage(MSG_END_BACKUP);
376         }
377 
378         @Override
onStartRestore()379         public void onStartRestore() throws RemoteException {
380             mHandler.sendEmptyMessage(MSG_START_RESTORE);
381         }
382 
383         @Override
onRestorePackage(String name)384         public void onRestorePackage(String name) throws RemoteException {
385             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESTORE_PACKAGE, name));
386         }
387 
388         @Override
onEndRestore()389         public void onEndRestore() throws RemoteException {
390             mHandler.sendEmptyMessage(MSG_END_RESTORE);
391         }
392 
393         @Override
onTimeout()394         public void onTimeout() throws RemoteException {
395             mHandler.sendEmptyMessage(MSG_TIMEOUT);
396         }
397     }
398 }
399