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