Home | History | Annotate | Download | only in settings
      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.ActivityInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.res.Resources.NotFoundException;
     27 import android.media.AudioManager;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Message;
     33 import android.os.PowerManager;
     34 import android.os.RemoteException;
     35 import android.os.ServiceManager;
     36 import android.os.SystemProperties;
     37 import android.os.UserHandle;
     38 import android.os.storage.IMountService;
     39 import android.os.storage.StorageManager;
     40 import android.provider.Settings;
     41 import android.telecom.TelecomManager;
     42 import android.telephony.TelephonyManager;
     43 import android.text.Editable;
     44 import android.text.TextUtils;
     45 import android.text.TextWatcher;
     46 import android.text.format.DateUtils;
     47 import android.util.Log;
     48 import android.view.KeyEvent;
     49 import android.view.MotionEvent;
     50 import android.view.View;
     51 import android.view.WindowManager;
     52 import android.view.View.OnClickListener;
     53 import android.view.View.OnKeyListener;
     54 import android.view.View.OnTouchListener;
     55 import android.view.inputmethod.EditorInfo;
     56 import android.view.inputmethod.InputMethodInfo;
     57 import android.view.inputmethod.InputMethodManager;
     58 import android.view.inputmethod.InputMethodSubtype;
     59 import android.widget.Button;
     60 import android.widget.EditText;
     61 import android.widget.ProgressBar;
     62 import android.widget.TextView;
     63 
     64 import com.android.internal.statusbar.StatusBarIcon;
     65 import com.android.internal.telephony.Phone;
     66 import com.android.internal.telephony.PhoneConstants;
     67 import com.android.internal.widget.LockPatternUtils;
     68 import com.android.internal.widget.LockPatternView;
     69 import com.android.internal.widget.LockPatternView.Cell;
     70 
     71 import static com.android.internal.widget.LockPatternView.DisplayMode;
     72 
     73 import java.util.List;
     74 
     75 /**
     76  * Settings screens to show the UI flows for encrypting/decrypting the device.
     77  *
     78  * This may be started via adb for debugging the UI layout, without having to go through
     79  * encryption flows everytime. It should be noted that starting the activity in this manner
     80  * is only useful for verifying UI-correctness - the behavior will not be identical.
     81  * <pre>
     82  * $ adb shell pm enable com.android.settings/.CryptKeeper
     83  * $ adb shell am start \
     84  *     -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "progress" \
     85  *     -n com.android.settings/.CryptKeeper
     86  * </pre>
     87  */
     88 public class CryptKeeper extends Activity implements TextView.OnEditorActionListener,
     89         OnKeyListener, OnTouchListener, TextWatcher {
     90     private static final String TAG = "CryptKeeper";
     91 
     92     private static final String DECRYPT_STATE = "trigger_restart_framework";
     93     /** Message sent to us to indicate encryption update progress. */
     94     private static final int MESSAGE_UPDATE_PROGRESS = 1;
     95     /** Message sent to us to cool-down (waste user's time between password attempts) */
     96     private static final int MESSAGE_COOLDOWN = 2;
     97     /** Message sent to us to indicate alerting the user that we are waiting for password entry */
     98     private static final int MESSAGE_NOTIFY = 3;
     99 
    100     // Constants used to control policy.
    101     private static final int MAX_FAILED_ATTEMPTS = 30;
    102     private static final int COOL_DOWN_ATTEMPTS = 10;
    103     private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
    104 
    105     // Intent action for launching the Emergency Dialer activity.
    106     static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
    107 
    108     // Debug Intent extras so that this Activity may be started via adb for debugging UI layouts
    109     private static final String EXTRA_FORCE_VIEW =
    110             "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW";
    111     private static final String FORCE_VIEW_PROGRESS = "progress";
    112     private static final String FORCE_VIEW_ERROR = "error";
    113     private static final String FORCE_VIEW_PASSWORD = "password";
    114 
    115     /** When encryption is detected, this flag indicates whether or not we've checked for errors. */
    116     private boolean mValidationComplete;
    117     private boolean mValidationRequested;
    118     /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
    119     private boolean mEncryptionGoneBad;
    120     /** If gone bad, should we show encryption failed (false) or corrupt (true)*/
    121     private boolean mCorrupt;
    122     /** A flag to indicate when the back event should be ignored */
    123     private boolean mIgnoreBack = false;
    124     private int mCooldown;
    125     PowerManager.WakeLock mWakeLock;
    126     private EditText mPasswordEntry;
    127     private LockPatternView mLockPatternView;
    128     /** Number of calls to {@link #notifyUser()} to ignore before notifying. */
    129     private int mNotificationCountdown = 0;
    130     /** Number of calls to {@link #notifyUser()} before we release the wakelock */
    131     private int mReleaseWakeLockCountdown = 0;
    132     private int mStatusString = R.string.enter_password;
    133 
    134     // how long we wait to clear a wrong pattern
    135     private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 1500;
    136 
    137     // how long we wait to clear a right pattern
    138     private static final int RIGHT_PATTERN_CLEAR_TIMEOUT_MS = 500;
    139 
    140     // When the user enters a short pin/password, run this to show an error,
    141     // but don't count it against attempts.
    142     private final Runnable mFakeUnlockAttemptRunnable = new Runnable() {
    143         public void run() {
    144             handleBadAttempt(1 /* failedAttempt */);
    145         }
    146     };
    147 
    148     // TODO: this should be tuned to match minimum decryption timeout
    149     private static final int FAKE_ATTEMPT_DELAY = 1000;
    150 
    151     private Runnable mClearPatternRunnable = new Runnable() {
    152         public void run() {
    153             mLockPatternView.clearPattern();
    154         }
    155     };
    156 
    157     /**
    158      * Used to propagate state through configuration changes (e.g. screen rotation)
    159      */
    160     private static class NonConfigurationInstanceState {
    161         final PowerManager.WakeLock wakelock;
    162 
    163         NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
    164             wakelock = _wakelock;
    165         }
    166     }
    167 
    168     private class DecryptTask extends AsyncTask<String, Void, Integer> {
    169         private void hide(int id) {
    170             View view = findViewById(id);
    171             if (view != null) {
    172                 view.setVisibility(View.GONE);
    173             }
    174         }
    175 
    176         @Override
    177         protected Integer doInBackground(String... params) {
    178             final IMountService service = getMountService();
    179             try {
    180                 return service.decryptStorage(params[0]);
    181             } catch (Exception e) {
    182                 Log.e(TAG, "Error while decrypting...", e);
    183                 return -1;
    184             }
    185         }
    186 
    187         @Override
    188         protected void onPostExecute(Integer failedAttempts) {
    189             if (failedAttempts == 0) {
    190                 // The password was entered successfully. Simply do nothing
    191                 // and wait for the service restart to switch to surfacefligner
    192                 if (mLockPatternView != null) {
    193                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
    194                     mLockPatternView.postDelayed(mClearPatternRunnable, RIGHT_PATTERN_CLEAR_TIMEOUT_MS);
    195                 }
    196                 hide(R.id.passwordEntry);
    197                 hide(R.id.switch_ime_button);
    198                 hide(R.id.lockPattern);
    199                 hide(R.id.status);
    200                 hide(R.id.owner_info);
    201                 hide(R.id.emergencyCallButton);
    202             } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
    203                 // Factory reset the device.
    204                 Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
    205                 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    206                 intent.putExtra(Intent.EXTRA_REASON, "CryptKeeper.MAX_FAILED_ATTEMPTS");
    207                 sendBroadcast(intent);
    208             } else if (failedAttempts == -1) {
    209                 // Right password, but decryption failed. Tell user bad news ...
    210                 setContentView(R.layout.crypt_keeper_progress);
    211                 showFactoryReset(true);
    212                 return;
    213             } else {
    214                 handleBadAttempt(failedAttempts);
    215             }
    216         }
    217     }
    218 
    219     private void handleBadAttempt(Integer failedAttempts) {
    220         // Wrong entry. Handle pattern case.
    221         if (mLockPatternView != null) {
    222             mLockPatternView.setDisplayMode(DisplayMode.Wrong);
    223             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    224             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
    225         }
    226         if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
    227             mCooldown = COOL_DOWN_INTERVAL;
    228             cooldown();
    229         } else {
    230             final TextView status = (TextView) findViewById(R.id.status);
    231 
    232             int remainingAttempts = MAX_FAILED_ATTEMPTS - failedAttempts;
    233             if (remainingAttempts < COOL_DOWN_ATTEMPTS) {
    234                 CharSequence warningTemplate = getText(R.string.crypt_keeper_warn_wipe);
    235                 CharSequence warning = TextUtils.expandTemplate(warningTemplate,
    236                         Integer.toString(remainingAttempts));
    237                 status.setText(warning);
    238             } else {
    239                 status.setText(R.string.try_again);
    240             }
    241 
    242             if (mLockPatternView != null) {
    243                 mLockPatternView.setDisplayMode(DisplayMode.Wrong);
    244             }
    245             // Reenable the password entry
    246             if (mPasswordEntry != null) {
    247                 mPasswordEntry.setEnabled(true);
    248                 final InputMethodManager imm = (InputMethodManager) getSystemService(
    249                         Context.INPUT_METHOD_SERVICE);
    250                 imm.showSoftInput(mPasswordEntry, 0);
    251                 setBackFunctionality(true);
    252             }
    253             if (mLockPatternView != null) {
    254                 mLockPatternView.setEnabled(true);
    255             }
    256         }
    257     }
    258 
    259     private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
    260         int state;
    261 
    262         @Override
    263         protected Boolean doInBackground(Void... params) {
    264             final IMountService service = getMountService();
    265             try {
    266                 Log.d(TAG, "Validating encryption state.");
    267                 state = service.getEncryptionState();
    268                 if (state == IMountService.ENCRYPTION_STATE_NONE) {
    269                     Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
    270                     return true; // Unexpected, but fine, I guess...
    271                 }
    272                 return state == IMountService.ENCRYPTION_STATE_OK;
    273             } catch (RemoteException e) {
    274                 Log.w(TAG, "Unable to get encryption state properly");
    275                 return true;
    276             }
    277         }
    278 
    279         @Override
    280         protected void onPostExecute(Boolean result) {
    281             mValidationComplete = true;
    282             if (Boolean.FALSE.equals(result)) {
    283                 Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
    284                 mEncryptionGoneBad = true;
    285                 mCorrupt = state == IMountService.ENCRYPTION_STATE_ERROR_CORRUPT;
    286             } else {
    287                 Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
    288             }
    289             setupUi();
    290         }
    291     }
    292 
    293     private final Handler mHandler = new Handler() {
    294         @Override
    295         public void handleMessage(Message msg) {
    296             switch (msg.what) {
    297             case MESSAGE_UPDATE_PROGRESS:
    298                 updateProgress();
    299                 break;
    300 
    301             case MESSAGE_COOLDOWN:
    302                 cooldown();
    303                 break;
    304 
    305             case MESSAGE_NOTIFY:
    306                 notifyUser();
    307                 break;
    308             }
    309         }
    310     };
    311 
    312     private AudioManager mAudioManager;
    313     /** The status bar where back/home/recent buttons are shown. */
    314     private StatusBarManager mStatusBar;
    315 
    316     /** All the widgets to disable in the status bar */
    317     final private static int sWidgetsToDisable = StatusBarManager.DISABLE_EXPAND
    318             | StatusBarManager.DISABLE_NOTIFICATION_ICONS
    319             | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
    320             | StatusBarManager.DISABLE_SYSTEM_INFO
    321             | StatusBarManager.DISABLE_HOME
    322             | StatusBarManager.DISABLE_SEARCH
    323             | StatusBarManager.DISABLE_RECENT;
    324 
    325     protected static final int MIN_LENGTH_BEFORE_REPORT = LockPatternUtils.MIN_LOCK_PATTERN_SIZE;
    326 
    327     /** @return whether or not this Activity was started for debugging the UI only. */
    328     private boolean isDebugView() {
    329         return getIntent().hasExtra(EXTRA_FORCE_VIEW);
    330     }
    331 
    332     /** @return whether or not this Activity was started for debugging the specific UI view only. */
    333     private boolean isDebugView(String viewType /* non-nullable */) {
    334         return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
    335     }
    336 
    337     /**
    338      * Notify the user that we are awaiting input. Currently this sends an audio alert.
    339      */
    340     private void notifyUser() {
    341         if (mNotificationCountdown > 0) {
    342             --mNotificationCountdown;
    343         } else if (mAudioManager != null) {
    344             try {
    345                 // Play the standard keypress sound at full volume. This should be available on
    346                 // every device. We cannot play a ringtone here because media services aren't
    347                 // available yet. A DTMF-style tone is too soft to be noticed, and might not exist
    348                 // on tablet devices. The idea is to alert the user that something is needed: this
    349                 // does not have to be pleasing.
    350                 mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100);
    351             } catch (Exception e) {
    352                 Log.w(TAG, "notifyUser: Exception while playing sound: " + e);
    353             }
    354         }
    355         // Notify the user again in 5 seconds.
    356         mHandler.removeMessages(MESSAGE_NOTIFY);
    357         mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000);
    358 
    359         if (mWakeLock.isHeld()) {
    360             if (mReleaseWakeLockCountdown > 0) {
    361                 --mReleaseWakeLockCountdown;
    362             } else {
    363                 mWakeLock.release();
    364             }
    365         }
    366     }
    367 
    368     /**
    369      * Ignore back events after the user has entered the decrypt screen and while the device is
    370      * encrypting.
    371      */
    372     @Override
    373     public void onBackPressed() {
    374         // In the rare case that something pressed back even though we were disabled.
    375         if (mIgnoreBack)
    376             return;
    377         super.onBackPressed();
    378     }
    379 
    380     @Override
    381     public void onCreate(Bundle savedInstanceState) {
    382         super.onCreate(savedInstanceState);
    383 
    384         // If we are not encrypted or encrypting, get out quickly.
    385         final String state = SystemProperties.get("vold.decrypt");
    386         if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
    387             // Disable the crypt keeper.
    388             PackageManager pm = getPackageManager();
    389             ComponentName name = new ComponentName(this, CryptKeeper.class);
    390             pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
    391                     PackageManager.DONT_KILL_APP);
    392             // Typically CryptKeeper is launched as the home app.  We didn't
    393             // want to be running, so need to finish this activity.  We can count
    394             // on the activity manager re-launching the new home app upon finishing
    395             // this one, since this will leave the activity stack empty.
    396             // NOTE: This is really grungy.  I think it would be better for the
    397             // activity manager to explicitly launch the crypt keeper instead of
    398             // home in the situation where we need to decrypt the device
    399             finish();
    400             return;
    401         }
    402 
    403         try {
    404             if (getResources().getBoolean(R.bool.crypt_keeper_allow_rotation)) {
    405                 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
    406             }
    407         } catch (NotFoundException e) {
    408         }
    409 
    410         // Disable the status bar, but do NOT disable back because the user needs a way to go
    411         // from keyboard settings and back to the password screen.
    412         mStatusBar = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
    413         mStatusBar.disable(sWidgetsToDisable);
    414 
    415         setAirplaneModeIfNecessary();
    416         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    417         // Check for (and recover) retained instance data
    418         final Object lastInstance = getLastNonConfigurationInstance();
    419         if (lastInstance instanceof NonConfigurationInstanceState) {
    420             NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
    421             mWakeLock = retained.wakelock;
    422             Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
    423         }
    424     }
    425 
    426     /**
    427      * Note, we defer the state check and screen setup to onStart() because this will be
    428      * re-run if the user clicks the power button (sleeping/waking the screen), and this is
    429      * especially important if we were to lose the wakelock for any reason.
    430      */
    431     @Override
    432     public void onStart() {
    433         super.onStart();
    434         setupUi();
    435     }
    436 
    437     /**
    438      * Initializes the UI based on the current state of encryption.
    439      * This is idempotent - calling repeatedly will simply re-initialize the UI.
    440      */
    441     private void setupUi() {
    442         if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
    443             setContentView(R.layout.crypt_keeper_progress);
    444             showFactoryReset(mCorrupt);
    445             return;
    446         }
    447 
    448         final String progress = SystemProperties.get("vold.encrypt_progress");
    449         if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
    450             setContentView(R.layout.crypt_keeper_progress);
    451             encryptionProgressInit();
    452         } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
    453             new AsyncTask<Void, Void, Void>() {
    454                 int type = StorageManager.CRYPT_TYPE_PASSWORD;
    455                 String owner_info;
    456                 boolean pattern_visible;
    457 
    458                 @Override
    459                 public Void doInBackground(Void... v) {
    460                     try {
    461                         final IMountService service = getMountService();
    462                         type = service.getPasswordType();
    463                         owner_info = service.getField("OwnerInfo");
    464                         pattern_visible = !("0".equals(service.getField("PatternVisible")));
    465                     } catch (Exception e) {
    466                         Log.e(TAG, "Error calling mount service " + e);
    467                     }
    468 
    469                     return null;
    470                 }
    471 
    472                 @Override
    473                 public void onPostExecute(java.lang.Void v) {
    474                     if(type == StorageManager.CRYPT_TYPE_PIN) {
    475                         setContentView(R.layout.crypt_keeper_pin_entry);
    476                         mStatusString = R.string.enter_pin;
    477                     } else if (type == StorageManager.CRYPT_TYPE_PATTERN) {
    478                         setContentView(R.layout.crypt_keeper_pattern_entry);
    479                         setBackFunctionality(false);
    480                         mStatusString = R.string.enter_pattern;
    481                     } else {
    482                         setContentView(R.layout.crypt_keeper_password_entry);
    483                         mStatusString = R.string.enter_password;
    484                     }
    485                     final TextView status = (TextView) findViewById(R.id.status);
    486                     status.setText(mStatusString);
    487 
    488                     final TextView ownerInfo = (TextView) findViewById(R.id.owner_info);
    489                     ownerInfo.setText(owner_info);
    490                     ownerInfo.setSelected(true); // Required for marquee'ing to work
    491 
    492                     passwordEntryInit();
    493 
    494                     if (mLockPatternView != null) {
    495                         mLockPatternView.setInStealthMode(!pattern_visible);
    496                     }
    497 
    498                     if (mCooldown > 0) {
    499                         setBackFunctionality(false);
    500                         cooldown(); // in case we are cooling down and coming back from emergency dialler
    501                     }
    502                 }
    503             }.execute();
    504         } else if (!mValidationRequested) {
    505             // We're supposed to be encrypted, but no validation has been done.
    506             new ValidationTask().execute((Void[]) null);
    507             mValidationRequested = true;
    508         }
    509     }
    510 
    511     @Override
    512     public void onStop() {
    513         super.onStop();
    514         mHandler.removeMessages(MESSAGE_COOLDOWN);
    515         mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
    516         mHandler.removeMessages(MESSAGE_NOTIFY);
    517     }
    518 
    519     /**
    520      * Reconfiguring, so propagate the wakelock to the next instance.  This runs between onStop()
    521      * and onDestroy() and only if we are changing configuration (e.g. rotation).  Also clears
    522      * mWakeLock so the subsequent call to onDestroy does not release it.
    523      */
    524     @Override
    525     public Object onRetainNonConfigurationInstance() {
    526         NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
    527         Log.d(TAG, "Handing wakelock off to NonConfigurationInstanceState");
    528         mWakeLock = null;
    529         return state;
    530     }
    531 
    532     @Override
    533     public void onDestroy() {
    534         super.onDestroy();
    535 
    536         if (mWakeLock != null) {
    537             Log.d(TAG, "Releasing and destroying wakelock");
    538             mWakeLock.release();
    539             mWakeLock = null;
    540         }
    541     }
    542 
    543     /**
    544      * Start encrypting the device.
    545      */
    546     private void encryptionProgressInit() {
    547         // Accquire a partial wakelock to prevent the device from sleeping. Note
    548         // we never release this wakelock as we will be restarted after the device
    549         // is encrypted.
    550         Log.d(TAG, "Encryption progress screen initializing.");
    551         if (mWakeLock == null) {
    552             Log.d(TAG, "Acquiring wakelock.");
    553             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    554             mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
    555             mWakeLock.acquire();
    556         }
    557 
    558         ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true);
    559         // Ignore all back presses from now, both hard and soft keys.
    560         setBackFunctionality(false);
    561         // Start the first run of progress manually. This method sets up messages to occur at
    562         // repeated intervals.
    563         updateProgress();
    564     }
    565 
    566     /**
    567      * Show factory reset screen allowing the user to reset their phone when
    568      * there is nothing else we can do
    569      * @param corrupt true if userdata is corrupt, false if encryption failed
    570      *        partway through
    571      */
    572     private void showFactoryReset(final boolean corrupt) {
    573         // Hide the encryption-bot to make room for the "factory reset" button
    574         findViewById(R.id.encroid).setVisibility(View.GONE);
    575 
    576         // Show the reset button, failure text, and a divider
    577         final Button button = (Button) findViewById(R.id.factory_reset);
    578         button.setVisibility(View.VISIBLE);
    579         button.setOnClickListener(new OnClickListener() {
    580                 @Override
    581             public void onClick(View v) {
    582                 // Factory reset the device.
    583                 Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
    584                 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    585                 intent.putExtra(Intent.EXTRA_REASON,
    586                         "CryptKeeper.showFactoryReset() corrupt=" + corrupt);
    587                 sendBroadcast(intent);
    588             }
    589         });
    590 
    591         // Alert the user of the failure.
    592         if (corrupt) {
    593             ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_data_corrupt_title);
    594             ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_data_corrupt_summary);
    595         } else {
    596             ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
    597             ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
    598         }
    599 
    600         final View view = findViewById(R.id.bottom_divider);
    601         // TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate.
    602         if (view != null) {
    603             view.setVisibility(View.VISIBLE);
    604         }
    605     }
    606 
    607     private void updateProgress() {
    608         final String state = SystemProperties.get("vold.encrypt_progress");
    609 
    610         if ("error_partially_encrypted".equals(state)) {
    611             showFactoryReset(false);
    612             return;
    613         }
    614 
    615         // Get status as percentage first
    616         CharSequence status = getText(R.string.crypt_keeper_setup_description);
    617         int percent = 0;
    618         try {
    619             // Force a 50% progress state when debugging the view.
    620             percent = isDebugView() ? 50 : Integer.parseInt(state);
    621         } catch (Exception e) {
    622             Log.w(TAG, "Error parsing progress: " + e.toString());
    623         }
    624         String progress = Integer.toString(percent);
    625 
    626         // Now try to get status as time remaining and replace as appropriate
    627         Log.v(TAG, "Encryption progress: " + progress);
    628         try {
    629             final String timeProperty = SystemProperties.get("vold.encrypt_time_remaining");
    630             int time = Integer.parseInt(timeProperty);
    631             if (time >= 0) {
    632                 // Round up to multiple of 10 - this way display is less jerky
    633                 time = (time + 9) / 10 * 10;
    634                 progress = DateUtils.formatElapsedTime(time);
    635                 status = getText(R.string.crypt_keeper_setup_time_remaining);
    636             }
    637         } catch (Exception e) {
    638             // Will happen if no time etc - show percentage
    639         }
    640 
    641         final TextView tv = (TextView) findViewById(R.id.status);
    642         if (tv != null) {
    643             tv.setText(TextUtils.expandTemplate(status, progress));
    644         }
    645 
    646         // Check the progress every 1 seconds
    647         mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
    648         mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 1000);
    649     }
    650 
    651     /** Disable password input for a while to force the user to waste time between retries */
    652     private void cooldown() {
    653         final TextView status = (TextView) findViewById(R.id.status);
    654 
    655         if (mCooldown <= 0) {
    656             // Re-enable the password entry and back presses.
    657             if (mPasswordEntry != null) {
    658                 mPasswordEntry.setEnabled(true);
    659                 final InputMethodManager imm = (InputMethodManager) getSystemService(
    660                                           Context.INPUT_METHOD_SERVICE);
    661                 imm.showSoftInput(mPasswordEntry, 0);
    662                 setBackFunctionality(true);
    663             }
    664             if (mLockPatternView != null) {
    665                 mLockPatternView.setEnabled(true);
    666             }
    667             status.setText(mStatusString);
    668         } else {
    669             // Disable the password entry and back presses.
    670             if (mPasswordEntry != null) {
    671                 mPasswordEntry.setEnabled(false);
    672             }
    673             if (mLockPatternView != null) {
    674                 mLockPatternView.setEnabled(false);
    675             }
    676 
    677             CharSequence template = getText(R.string.crypt_keeper_cooldown);
    678             status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
    679 
    680             mCooldown--;
    681             mHandler.removeMessages(MESSAGE_COOLDOWN);
    682             mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second
    683         }
    684     }
    685 
    686     /**
    687      * Sets the back status: enabled or disabled according to the parameter.
    688      * @param isEnabled true if back is enabled, false otherwise.
    689      */
    690     private final void setBackFunctionality(boolean isEnabled) {
    691         mIgnoreBack = !isEnabled;
    692         if (isEnabled) {
    693             mStatusBar.disable(sWidgetsToDisable);
    694         } else {
    695             mStatusBar.disable(sWidgetsToDisable | StatusBarManager.DISABLE_BACK);
    696         }
    697     }
    698 
    699     private void fakeUnlockAttempt(View postingView) {
    700         postingView.postDelayed(mFakeUnlockAttemptRunnable, FAKE_ATTEMPT_DELAY);
    701     }
    702 
    703     protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
    704         new LockPatternView.OnPatternListener() {
    705 
    706         @Override
    707         public void onPatternStart() {
    708             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    709         }
    710 
    711         @Override
    712         public void onPatternCleared() {
    713         }
    714 
    715         @Override
    716         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
    717             mLockPatternView.setEnabled(false);
    718             if (pattern.size() >= MIN_LENGTH_BEFORE_REPORT) {
    719                 new DecryptTask().execute(LockPatternUtils.patternToString(pattern));
    720             } else {
    721                 // Allow user to make as many of these as they want.
    722                 fakeUnlockAttempt(mLockPatternView);
    723             }
    724         }
    725 
    726         @Override
    727         public void onPatternCellAdded(List<Cell> pattern) {
    728         }
    729      };
    730 
    731      private void passwordEntryInit() {
    732         // Password/pin case
    733         mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
    734         if (mPasswordEntry != null){
    735             mPasswordEntry.setOnEditorActionListener(this);
    736             mPasswordEntry.requestFocus();
    737             // Become quiet when the user interacts with the Edit text screen.
    738             mPasswordEntry.setOnKeyListener(this);
    739             mPasswordEntry.setOnTouchListener(this);
    740             mPasswordEntry.addTextChangedListener(this);
    741         }
    742 
    743         // Pattern case
    744         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
    745         if (mLockPatternView != null) {
    746             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
    747         }
    748 
    749         // Disable the Emergency call button if the device has no voice telephone capability
    750         if (!getTelephonyManager().isVoiceCapable()) {
    751             final View emergencyCall = findViewById(R.id.emergencyCallButton);
    752             if (emergencyCall != null) {
    753                 Log.d(TAG, "Removing the emergency Call button");
    754                 emergencyCall.setVisibility(View.GONE);
    755             }
    756         }
    757 
    758         final View imeSwitcher = findViewById(R.id.switch_ime_button);
    759         final InputMethodManager imm = (InputMethodManager) getSystemService(
    760                 Context.INPUT_METHOD_SERVICE);
    761         if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
    762             imeSwitcher.setVisibility(View.VISIBLE);
    763             imeSwitcher.setOnClickListener(new OnClickListener() {
    764                     @Override
    765                 public void onClick(View v) {
    766                     imm.showInputMethodPicker();
    767                 }
    768             });
    769         }
    770 
    771         // We want to keep the screen on while waiting for input. In minimal boot mode, the device
    772         // is completely non-functional, and we want the user to notice the device and enter a
    773         // password.
    774         if (mWakeLock == null) {
    775             Log.d(TAG, "Acquiring wakelock.");
    776             final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    777             if (pm != null) {
    778                 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
    779                 mWakeLock.acquire();
    780                 // Keep awake for 10 minutes - if the user hasn't been alerted by then
    781                 // best not to just drain their battery
    782                 mReleaseWakeLockCountdown = 96; // 96 * 5 secs per click + 120 secs before we show this = 600
    783             }
    784         }
    785 
    786         // Asynchronously throw up the IME, since there are issues with requesting it to be shown
    787         // immediately.
    788         if (mLockPatternView == null && mCooldown <= 0) {
    789             mHandler.postDelayed(new Runnable() {
    790                 @Override public void run() {
    791                     imm.showSoftInputUnchecked(0, null);
    792                 }
    793             }, 0);
    794         }
    795 
    796         updateEmergencyCallButtonState();
    797         // Notify the user in 120 seconds that we are waiting for him to enter the password.
    798         mHandler.removeMessages(MESSAGE_NOTIFY);
    799         mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);
    800 
    801         // Dismiss secure & non-secure keyguards while this screen is showing.
    802         getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
    803                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    804     }
    805 
    806     /**
    807      * Method adapted from com.android.inputmethod.latin.Utils
    808      *
    809      * @param imm The input method manager
    810      * @param shouldIncludeAuxiliarySubtypes
    811      * @return true if we have multiple IMEs to choose from
    812      */
    813     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
    814             final boolean shouldIncludeAuxiliarySubtypes) {
    815         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    816 
    817         // Number of the filtered IMEs
    818         int filteredImisCount = 0;
    819 
    820         for (InputMethodInfo imi : enabledImis) {
    821             // We can return true immediately after we find two or more filtered IMEs.
    822             if (filteredImisCount > 1) return true;
    823             final List<InputMethodSubtype> subtypes =
    824                     imm.getEnabledInputMethodSubtypeList(imi, true);
    825             // IMEs that have no subtypes should be counted.
    826             if (subtypes.isEmpty()) {
    827                 ++filteredImisCount;
    828                 continue;
    829             }
    830 
    831             int auxCount = 0;
    832             for (InputMethodSubtype subtype : subtypes) {
    833                 if (subtype.isAuxiliary()) {
    834                     ++auxCount;
    835                 }
    836             }
    837             final int nonAuxCount = subtypes.size() - auxCount;
    838 
    839             // IMEs that have one or more non-auxiliary subtypes should be counted.
    840             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
    841             // subtypes should be counted as well.
    842             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
    843                 ++filteredImisCount;
    844                 continue;
    845             }
    846         }
    847 
    848         return filteredImisCount > 1
    849         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
    850         // input method subtype (The current IME should be LatinIME.)
    851                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
    852     }
    853 
    854     private IMountService getMountService() {
    855         final IBinder service = ServiceManager.getService("mount");
    856         if (service != null) {
    857             return IMountService.Stub.asInterface(service);
    858         }
    859         return null;
    860     }
    861 
    862     @Override
    863     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    864         if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
    865             // Get the password
    866             final String password = v.getText().toString();
    867 
    868             if (TextUtils.isEmpty(password)) {
    869                 return true;
    870             }
    871 
    872             // Now that we have the password clear the password field.
    873             v.setText(null);
    874 
    875             // Disable the password entry and back keypress while checking the password. These
    876             // we either be re-enabled if the password was wrong or after the cooldown period.
    877             mPasswordEntry.setEnabled(false);
    878             setBackFunctionality(false);
    879 
    880             if (password.length() >= LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
    881                 Log.d(TAG, "Attempting to send command to decrypt");
    882                 new DecryptTask().execute(password);
    883             } else {
    884                 // Allow user to make as many of these as they want.
    885                 fakeUnlockAttempt(mPasswordEntry);
    886             }
    887 
    888             return true;
    889         }
    890         return false;
    891     }
    892 
    893     /**
    894      * Set airplane mode on the device if it isn't an LTE device.
    895      * Full story: In minimal boot mode, we cannot save any state. In particular, we cannot save
    896      * any incoming SMS's. So SMSs that are received here will be silently dropped to the floor.
    897      * That is bad. Also, we cannot receive any telephone calls in this state. So to avoid
    898      * both these problems, we turn the radio off. However, on certain networks turning on and
    899      * off the radio takes a long time. In such cases, we are better off leaving the radio
    900      * running so the latency of an E911 call is short.
    901      * The behavior after this is:
    902      * 1. Emergency dialing: the emergency dialer has logic to force the device out of
    903      *    airplane mode and restart the radio.
    904      * 2. Full boot: we read the persistent settings from the previous boot and restore the
    905      *    radio to whatever it was before it restarted. This also happens when rebooting a
    906      *    phone that has no encryption.
    907      */
    908     private final void setAirplaneModeIfNecessary() {
    909         final boolean isLteDevice =
    910                 getTelephonyManager().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
    911         if (!isLteDevice) {
    912             Log.d(TAG, "Going into airplane mode.");
    913             Settings.Global.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
    914             final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    915             intent.putExtra("state", true);
    916             sendBroadcastAsUser(intent, UserHandle.ALL);
    917         }
    918     }
    919 
    920     /**
    921      * Code to update the state of, and handle clicks from, the "Emergency call" button.
    922      *
    923      * This code is mostly duplicated from the corresponding code in
    924      * LockPatternUtils and LockPatternKeyguardView under frameworks/base.
    925      */
    926     private void updateEmergencyCallButtonState() {
    927         final Button emergencyCall = (Button) findViewById(R.id.emergencyCallButton);
    928         // The button isn't present at all in some configurations.
    929         if (emergencyCall == null)
    930             return;
    931 
    932         if (isEmergencyCallCapable()) {
    933             emergencyCall.setVisibility(View.VISIBLE);
    934             emergencyCall.setOnClickListener(new View.OnClickListener() {
    935                     @Override
    936 
    937                     public void onClick(View v) {
    938                         takeEmergencyCallAction();
    939                     }
    940                 });
    941         } else {
    942             emergencyCall.setVisibility(View.GONE);
    943             return;
    944         }
    945 
    946         int textId;
    947         if (getTelecomManager().isInCall()) {
    948             // Show "return to call"
    949             textId = R.string.cryptkeeper_return_to_call;
    950         } else {
    951             textId = R.string.cryptkeeper_emergency_call;
    952         }
    953         emergencyCall.setText(textId);
    954     }
    955 
    956     private boolean isEmergencyCallCapable() {
    957         return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
    958     }
    959 
    960     private void takeEmergencyCallAction() {
    961         TelecomManager telecomManager = getTelecomManager();
    962         if (telecomManager.isInCall()) {
    963             telecomManager.showInCallScreen(false /* showDialpad */);
    964         } else {
    965             launchEmergencyDialer();
    966         }
    967     }
    968 
    969 
    970     private void launchEmergencyDialer() {
    971         final Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
    972         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    973                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    974         setBackFunctionality(true);
    975         startActivity(intent);
    976     }
    977 
    978     private TelephonyManager getTelephonyManager() {
    979         return (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    980     }
    981 
    982     private TelecomManager getTelecomManager() {
    983         return (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
    984     }
    985 
    986     /**
    987      * Listen to key events so we can disable sounds when we get a keyinput in EditText.
    988      */
    989     private void delayAudioNotification() {
    990         mNotificationCountdown = 20;
    991     }
    992 
    993     @Override
    994     public boolean onKey(View v, int keyCode, KeyEvent event) {
    995         delayAudioNotification();
    996         return false;
    997     }
    998 
    999     @Override
   1000     public boolean onTouch(View v, MotionEvent event) {
   1001         delayAudioNotification();
   1002         return false;
   1003     }
   1004 
   1005     @Override
   1006     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   1007         return;
   1008     }
   1009 
   1010     @Override
   1011     public void onTextChanged(CharSequence s, int start, int before, int count) {
   1012         delayAudioNotification();
   1013     }
   1014 
   1015     @Override
   1016     public void afterTextChanged(Editable s) {
   1017         return;
   1018     }
   1019 }
   1020