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