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