• 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.settings;
18 
19 import android.app.Activity;
20 import android.app.StatusBarManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Message;
30 import android.os.PowerManager;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.SystemProperties;
34 import android.os.storage.IMountService;
35 import android.telephony.TelephonyManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.View;
40 import android.view.View.OnClickListener;
41 import android.view.inputmethod.EditorInfo;
42 import android.view.inputmethod.InputMethodInfo;
43 import android.view.inputmethod.InputMethodManager;
44 import android.view.inputmethod.InputMethodSubtype;
45 import android.widget.Button;
46 import android.widget.EditText;
47 import android.widget.ProgressBar;
48 import android.widget.TextView;
49 
50 import com.android.internal.telephony.ITelephony;
51 
52 import java.util.List;
53 
54 /**
55  * Settings screens to show the UI flows for encrypting/decrypting the device.
56  *
57  * This may be started via adb for debugging the UI layout, without having to go through
58  * encryption flows everytime. It should be noted that starting the activity in this manner
59  * is only useful for verifying UI-correctness - the behavior will not be identical.
60  * <pre>
61  * $ adb shell pm enable com.android.settings/.CryptKeeper
62  * $ adb shell am start \
63  *     -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "progress" \
64  *     -n com.android.settings/.CryptKeeper
65  * </pre>
66  */
67 public class CryptKeeper extends Activity implements TextView.OnEditorActionListener {
68     private static final String TAG = "CryptKeeper";
69 
70     private static final String DECRYPT_STATE = "trigger_restart_framework";
71 
72     private static final int UPDATE_PROGRESS = 1;
73     private static final int COOLDOWN = 2;
74 
75     private static final int MAX_FAILED_ATTEMPTS = 30;
76     private static final int COOL_DOWN_ATTEMPTS = 10;
77     private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
78 
79     // Intent action for launching the Emergency Dialer activity.
80     static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
81 
82     // Debug Intent extras so that this Activity may be started via adb for debugging UI layouts
83     private static final String EXTRA_FORCE_VIEW =
84             "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW";
85     private static final String FORCE_VIEW_PROGRESS = "progress";
86     private static final String FORCE_VIEW_ENTRY = "entry";
87     private static final String FORCE_VIEW_ERROR = "error";
88 
89     /** When encryption is detected, this flag indivates whether or not we've checked for erros. */
90     private boolean mValidationComplete;
91     private boolean mValidationRequested;
92     /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
93     private boolean mEncryptionGoneBad;
94 
95     private int mCooldown;
96     PowerManager.WakeLock mWakeLock;
97     private EditText mPasswordEntry;
98 
99     /**
100      * Used to propagate state through configuration changes (e.g. screen rotation)
101      */
102     private static class NonConfigurationInstanceState {
103         final PowerManager.WakeLock wakelock;
104 
NonConfigurationInstanceState(PowerManager.WakeLock _wakelock)105         NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
106             wakelock = _wakelock;
107         }
108     }
109 
110     // This activity is used to fade the screen to black after the password is entered.
111     public static class Blank extends Activity {
112         @Override
onCreate(Bundle savedInstanceState)113         public void onCreate(Bundle savedInstanceState) {
114             super.onCreate(savedInstanceState);
115             setContentView(R.layout.crypt_keeper_blank);
116         }
117     }
118 
119     private class DecryptTask extends AsyncTask<String, Void, Integer> {
120         @Override
doInBackground(String... params)121         protected Integer doInBackground(String... params) {
122             IMountService service = getMountService();
123             try {
124                 return service.decryptStorage(params[0]);
125             } catch (Exception e) {
126                 Log.e(TAG, "Error while decrypting...", e);
127                 return -1;
128             }
129         }
130 
131         @Override
onPostExecute(Integer failedAttempts)132         protected void onPostExecute(Integer failedAttempts) {
133             if (failedAttempts == 0) {
134                 // The password was entered successfully. Start the Blank activity
135                 // so this activity animates to black before the devices starts. Note
136                 // It has 1 second to complete the animation or it will be frozen
137                 // until the boot animation comes back up.
138                 Intent intent = new Intent(CryptKeeper.this, Blank.class);
139                 finish();
140                 startActivity(intent);
141             } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
142                 // Factory reset the device.
143                 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
144             } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
145                 mCooldown = COOL_DOWN_INTERVAL;
146                 cooldown();
147             } else {
148                 TextView tv = (TextView) findViewById(R.id.status);
149                 tv.setText(R.string.try_again);
150                 tv.setVisibility(View.VISIBLE);
151 
152                 // Reenable the password entry
153                 mPasswordEntry.setEnabled(true);
154             }
155         }
156     }
157 
158     private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
159         @Override
doInBackground(Void... params)160         protected Boolean doInBackground(Void... params) {
161             IMountService service = getMountService();
162             try {
163                 Log.d(TAG, "Validating encryption state.");
164                 int state = service.getEncryptionState();
165                 if (state == IMountService.ENCRYPTION_STATE_NONE) {
166                     Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
167                     return true; // Unexpected, but fine, I guess...
168                 }
169                 return state == IMountService.ENCRYPTION_STATE_OK;
170             } catch (RemoteException e) {
171                 Log.w(TAG, "Unable to get encryption state properly");
172                 return true;
173             }
174         }
175 
176         @Override
onPostExecute(Boolean result)177         protected void onPostExecute(Boolean result) {
178             mValidationComplete = true;
179             if (Boolean.FALSE.equals(result)) {
180                 Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
181                 mEncryptionGoneBad = true;
182             } else {
183                 Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
184             }
185             setupUi();
186         }
187     }
188 
189     private final Handler mHandler = new Handler() {
190         @Override
191         public void handleMessage(Message msg) {
192             switch (msg.what) {
193             case UPDATE_PROGRESS:
194                 updateProgress();
195                 break;
196 
197             case COOLDOWN:
198                 cooldown();
199                 break;
200             }
201         }
202     };
203 
204     /** @return whether or not this Activity was started for debugging the UI only. */
isDebugView()205     private boolean isDebugView() {
206         return getIntent().hasExtra(EXTRA_FORCE_VIEW);
207     }
208 
209     /** @return whether or not this Activity was started for debugging the specific UI view only. */
isDebugView(String viewType )210     private boolean isDebugView(String viewType /* non-nullable */) {
211         return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
212     }
213 
214     @Override
onCreate(Bundle savedInstanceState)215     public void onCreate(Bundle savedInstanceState) {
216         super.onCreate(savedInstanceState);
217 
218         // If we are not encrypted or encrypting, get out quickly.
219         String state = SystemProperties.get("vold.decrypt");
220         if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
221             // Disable the crypt keeper.
222             PackageManager pm = getPackageManager();
223             ComponentName name = new ComponentName(this, CryptKeeper.class);
224             pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
225                     PackageManager.DONT_KILL_APP);
226             // Typically CryptKeeper is launched as the home app.  We didn't
227             // want to be running, so need to finish this activity.  We can count
228             // on the activity manager re-launching the new home app upon finishing
229             // this one, since this will leave the activity stack empty.
230             // NOTE: This is really grungy.  I think it would be better for the
231             // activity manager to explicitly launch the crypt keeper instead of
232             // home in the situation where we need to decrypt the device
233             finish();
234             return;
235         }
236 
237         // Disable the status bar
238         StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
239         sbm.disable(StatusBarManager.DISABLE_EXPAND
240                 | StatusBarManager.DISABLE_NOTIFICATION_ICONS
241                 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
242                 | StatusBarManager.DISABLE_SYSTEM_INFO
243                 | StatusBarManager.DISABLE_HOME
244                 | StatusBarManager.DISABLE_RECENT
245                 | StatusBarManager.DISABLE_BACK);
246 
247         // Check for (and recover) retained instance data
248         Object lastInstance = getLastNonConfigurationInstance();
249         if (lastInstance instanceof NonConfigurationInstanceState) {
250             NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
251             mWakeLock = retained.wakelock;
252             Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
253         }
254     }
255 
256     /**
257      * Note, we defer the state check and screen setup to onStart() because this will be
258      * re-run if the user clicks the power button (sleeping/waking the screen), and this is
259      * especially important if we were to lose the wakelock for any reason.
260      */
261     @Override
onStart()262     public void onStart() {
263         super.onStart();
264 
265         setupUi();
266     }
267 
268     /**
269      * Initializes the UI based on the current state of encryption.
270      * This is idempotent - calling repeatedly will simply re-initialize the UI.
271      */
setupUi()272     private void setupUi() {
273         if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
274             setContentView(R.layout.crypt_keeper_progress);
275             showFactoryReset();
276             return;
277         }
278 
279         String progress = SystemProperties.get("vold.encrypt_progress");
280         if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
281             setContentView(R.layout.crypt_keeper_progress);
282             encryptionProgressInit();
283         } else if (mValidationComplete) {
284             setContentView(R.layout.crypt_keeper_password_entry);
285             passwordEntryInit();
286         } else if (!mValidationRequested) {
287             // We're supposed to be encrypted, but no validation has been done.
288             new ValidationTask().execute((Void[]) null);
289             mValidationRequested = true;
290         }
291     }
292 
293     @Override
onStop()294     public void onStop() {
295         super.onStop();
296 
297         mHandler.removeMessages(COOLDOWN);
298         mHandler.removeMessages(UPDATE_PROGRESS);
299     }
300 
301     /**
302      * Reconfiguring, so propagate the wakelock to the next instance.  This runs between onStop()
303      * and onDestroy() and only if we are changing configuration (e.g. rotation).  Also clears
304      * mWakeLock so the subsequent call to onDestroy does not release it.
305      */
306     @Override
onRetainNonConfigurationInstance()307     public Object onRetainNonConfigurationInstance() {
308         NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
309         Log.d(TAG, "Handing wakelock off to NonConfigurationInstanceState");
310         mWakeLock = null;
311         return state;
312     }
313 
314     @Override
onDestroy()315     public void onDestroy() {
316         super.onDestroy();
317 
318         if (mWakeLock != null) {
319             Log.d(TAG, "Releasing and destroying wakelock");
320             mWakeLock.release();
321             mWakeLock = null;
322         }
323     }
324 
encryptionProgressInit()325     private void encryptionProgressInit() {
326         // Accquire a partial wakelock to prevent the device from sleeping. Note
327         // we never release this wakelock as we will be restarted after the device
328         // is encrypted.
329 
330         Log.d(TAG, "Encryption progress screen initializing.");
331         if (mWakeLock == null) {
332             Log.d(TAG, "Acquiring wakelock.");
333             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
334             mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
335             mWakeLock.acquire();
336         }
337 
338         ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
339         progressBar.setIndeterminate(true);
340 
341         updateProgress();
342     }
343 
showFactoryReset()344     private void showFactoryReset() {
345         // Hide the encryption-bot to make room for the "factory reset" button
346         findViewById(R.id.encroid).setVisibility(View.GONE);
347 
348         // Show the reset button, failure text, and a divider
349         Button button = (Button) findViewById(R.id.factory_reset);
350         button.setVisibility(View.VISIBLE);
351         button.setOnClickListener(new OnClickListener() {
352             public void onClick(View v) {
353                 // Factory reset the device.
354                 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
355             }
356         });
357 
358         TextView tv = (TextView) findViewById(R.id.title);
359         tv.setText(R.string.crypt_keeper_failed_title);
360 
361         tv = (TextView) findViewById(R.id.status);
362         tv.setText(R.string.crypt_keeper_failed_summary);
363 
364         View view = findViewById(R.id.bottom_divider);
365         if (view != null) {
366             view.setVisibility(View.VISIBLE);
367         }
368     }
369 
updateProgress()370     private void updateProgress() {
371         String state = SystemProperties.get("vold.encrypt_progress");
372 
373         if ("error_partially_encrypted".equals(state)) {
374             showFactoryReset();
375             return;
376         }
377 
378         int progress = 0;
379         try {
380             // Force a 50% progress state when debugging the view.
381             progress = isDebugView() ? 50 : Integer.parseInt(state);
382         } catch (Exception e) {
383             Log.w(TAG, "Error parsing progress: " + e.toString());
384         }
385 
386         CharSequence status = getText(R.string.crypt_keeper_setup_description);
387         Log.v(TAG, "Encryption progress: " + progress);
388         TextView tv = (TextView) findViewById(R.id.status);
389         tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
390 
391         // Check the progress every 5 seconds
392         mHandler.removeMessages(UPDATE_PROGRESS);
393         mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000);
394     }
395 
cooldown()396     private void cooldown() {
397         TextView tv = (TextView) findViewById(R.id.status);
398 
399         if (mCooldown <= 0) {
400             // Re-enable the password entry
401             mPasswordEntry.setEnabled(true);
402 
403             tv.setVisibility(View.GONE);
404         } else {
405             CharSequence template = getText(R.string.crypt_keeper_cooldown);
406             tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
407 
408             tv.setVisibility(View.VISIBLE);
409 
410             mCooldown--;
411             mHandler.removeMessages(COOLDOWN);
412             mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
413         }
414     }
415 
passwordEntryInit()416     private void passwordEntryInit() {
417         mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
418         mPasswordEntry.setOnEditorActionListener(this);
419         mPasswordEntry.requestFocus();
420 
421         View imeSwitcher = findViewById(R.id.switch_ime_button);
422         final InputMethodManager imm = (InputMethodManager) getSystemService(
423                 Context.INPUT_METHOD_SERVICE);
424         if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
425             imeSwitcher.setVisibility(View.VISIBLE);
426             imeSwitcher.setOnClickListener(new OnClickListener() {
427                 public void onClick(View v) {
428                     imm.showInputMethodPicker();
429                 }
430             });
431         }
432 
433         // Asynchronously throw up the IME, since there are issues with requesting it to be shown
434         // immediately.
435         mHandler.postDelayed(new Runnable() {
436             @Override public void run() {
437                 imm.showSoftInputUnchecked(0, null);
438             }
439         }, 0);
440 
441         updateEmergencyCallButtonState();
442     }
443 
444     /**
445      * Method adapted from com.android.inputmethod.latin.Utils
446      *
447      * @param imm The input method manager
448      * @param shouldIncludeAuxiliarySubtypes
449      * @return true if we have multiple IMEs to choose from
450      */
hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes)451     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
452             final boolean shouldIncludeAuxiliarySubtypes) {
453         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
454 
455         // Number of the filtered IMEs
456         int filteredImisCount = 0;
457 
458         for (InputMethodInfo imi : enabledImis) {
459             // We can return true immediately after we find two or more filtered IMEs.
460             if (filteredImisCount > 1) return true;
461             final List<InputMethodSubtype> subtypes =
462                     imm.getEnabledInputMethodSubtypeList(imi, true);
463             // IMEs that have no subtypes should be counted.
464             if (subtypes.isEmpty()) {
465                 ++filteredImisCount;
466                 continue;
467             }
468 
469             int auxCount = 0;
470             for (InputMethodSubtype subtype : subtypes) {
471                 if (subtype.isAuxiliary()) {
472                     ++auxCount;
473                 }
474             }
475             final int nonAuxCount = subtypes.size() - auxCount;
476 
477             // IMEs that have one or more non-auxiliary subtypes should be counted.
478             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
479             // subtypes should be counted as well.
480             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
481                 ++filteredImisCount;
482                 continue;
483             }
484         }
485 
486         return filteredImisCount > 1
487         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
488         // input method subtype (The current IME should be LatinIME.)
489                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
490     }
491 
getMountService()492     private IMountService getMountService() {
493         IBinder service = ServiceManager.getService("mount");
494         if (service != null) {
495             return IMountService.Stub.asInterface(service);
496         }
497         return null;
498     }
499 
500     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)501     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
502         if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
503             // Get the password
504             String password = v.getText().toString();
505 
506             if (TextUtils.isEmpty(password)) {
507                 return true;
508             }
509 
510             // Now that we have the password clear the password field.
511             v.setText(null);
512 
513             // Disable the password entry while checking the password. This
514             // we either be reenabled if the password was wrong or after the
515             // cooldown period.
516             mPasswordEntry.setEnabled(false);
517 
518             Log.d(TAG, "Attempting to send command to decrypt");
519             new DecryptTask().execute(password);
520 
521             return true;
522         }
523         return false;
524     }
525 
526     //
527     // Code to update the state of, and handle clicks from, the "Emergency call" button.
528     //
529     // This code is mostly duplicated from the corresponding code in
530     // LockPatternUtils and LockPatternKeyguardView under frameworks/base.
531     //
532 
updateEmergencyCallButtonState()533     private void updateEmergencyCallButtonState() {
534         Button button = (Button) findViewById(R.id.emergencyCallButton);
535         // The button isn't present at all in some configurations.
536         if (button == null) return;
537 
538         if (isEmergencyCallCapable()) {
539             button.setVisibility(View.VISIBLE);
540             button.setOnClickListener(new View.OnClickListener() {
541                     public void onClick(View v) {
542                         takeEmergencyCallAction();
543                     }
544                 });
545         } else {
546             button.setVisibility(View.GONE);
547             return;
548         }
549 
550         int newState = TelephonyManager.getDefault().getCallState();
551         int textId;
552         if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
553             // show "return to call" text and show phone icon
554             textId = R.string.cryptkeeper_return_to_call;
555             int phoneCallIcon = R.drawable.stat_sys_phone_call;
556             button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
557         } else {
558             textId = R.string.cryptkeeper_emergency_call;
559             int emergencyIcon = R.drawable.ic_emergency;
560             button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
561         }
562         button.setText(textId);
563     }
564 
isEmergencyCallCapable()565     private boolean isEmergencyCallCapable() {
566         return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
567     }
568 
takeEmergencyCallAction()569     private void takeEmergencyCallAction() {
570         if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
571             resumeCall();
572         } else {
573             launchEmergencyDialer();
574         }
575     }
576 
resumeCall()577     private void resumeCall() {
578         ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
579         if (phone != null) {
580             try {
581                 phone.showCallScreen();
582             } catch (RemoteException e) {
583                 Log.e(TAG, "Error calling ITelephony service: " + e);
584             }
585         }
586     }
587 
launchEmergencyDialer()588     private void launchEmergencyDialer() {
589         Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
590         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
591                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
592         startActivity(intent);
593     }
594 }
595