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