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