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.storage.IMountService;
36import android.provider.Settings;
37import android.telephony.TelephonyManager;
38import android.text.Editable;
39import android.text.TextUtils;
40import android.text.TextWatcher;
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.MotionEvent;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.view.View.OnKeyListener;
47import android.view.View.OnTouchListener;
48import android.view.inputmethod.EditorInfo;
49import android.view.inputmethod.InputMethodInfo;
50import android.view.inputmethod.InputMethodManager;
51import android.view.inputmethod.InputMethodSubtype;
52import android.widget.Button;
53import android.widget.EditText;
54import android.widget.ProgressBar;
55import android.widget.TextView;
56
57import com.android.internal.telephony.ITelephony;
58import com.android.internal.telephony.Phone;
59
60import 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 */
75public 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
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
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
137        public void onBackPressed() {
138            return;
139        }
140    }
141
142    private class DecryptTask extends AsyncTask<String, Void, Integer> {
143        @Override
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
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
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
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. */
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. */
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     */
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
271    public void onBackPressed() {
272        if (mIgnoreBack)
273            return;
274        super.onBackPressed();
275    }
276
277    @Override
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
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     */
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
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
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
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     */
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
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
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 */
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
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     */
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
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
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     */
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     */
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
691    private boolean isEmergencyCallCapable() {
692        return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
693    }
694
695    private void takeEmergencyCallAction() {
696        if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
697            resumeCall();
698        } else {
699            launchEmergencyDialer();
700        }
701    }
702
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
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     */
724    private void delayAudioNotification() {
725        Log.d(TAG, "User entering password: delay audio notification");
726        mNotificationCountdown = 20;
727    }
728
729    @Override
730    public boolean onKey(View v, int keyCode, KeyEvent event) {
731        delayAudioNotification();
732        return false;
733    }
734
735    @Override
736    public boolean onTouch(View v, MotionEvent event) {
737        delayAudioNotification();
738        return false;
739    }
740
741    @Override
742    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
743        return;
744    }
745
746    @Override
747    public void onTextChanged(CharSequence s, int start, int before, int count) {
748        delayAudioNotification();
749    }
750
751    @Override
752    public void afterTextChanged(Editable s) {
753        return;
754    }
755}
756