blob: 4cab767a16afc7d4fcb2884d1734f9c4e24215e9 [file] [log] [blame]
Jason parks8fd5bc92011-01-12 16:03:31 -06001/*
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
Jason parks8fd5bc92011-01-12 16:03:31 -060019import android.app.Activity;
20import android.app.StatusBarManager;
21import android.content.ComponentName;
22import android.content.Context;
Jason parksec5a45e2011-01-18 15:28:36 -060023import android.content.Intent;
Jason parks8fd5bc92011-01-12 16:03:31 -060024import android.content.pm.PackageManager;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -070025import android.media.AudioManager;
Jason parks06c5ff42011-03-01 10:17:40 -060026import android.os.AsyncTask;
Jason parks8fd5bc92011-01-12 16:03:31 -060027import android.os.Bundle;
Jason parksec5a45e2011-01-18 15:28:36 -060028import android.os.Handler;
Jason parks8fd5bc92011-01-12 16:03:31 -060029import android.os.IBinder;
Jason parksec5a45e2011-01-18 15:28:36 -060030import android.os.Message;
Jason parks35933812011-01-21 15:48:20 -060031import android.os.PowerManager;
David Brown8373b452011-06-20 12:38:45 -070032import android.os.RemoteException;
Jason parks8fd5bc92011-01-12 16:03:31 -060033import android.os.ServiceManager;
34import android.os.SystemProperties;
35import android.os.storage.IMountService;
Vikram Aggarwal6ebbd302012-05-04 14:09:52 -070036import android.provider.Settings;
David Brown8373b452011-06-20 12:38:45 -070037import android.telephony.TelephonyManager;
Vikram Aggarwald1147252012-05-08 13:52:30 -070038import android.text.Editable;
Jason parksec5a45e2011-01-18 15:28:36 -060039import android.text.TextUtils;
Vikram Aggarwald1147252012-05-08 13:52:30 -070040import android.text.TextWatcher;
Jason parks8fd5bc92011-01-12 16:03:31 -060041import android.util.Log;
42import android.view.KeyEvent;
Vikram Aggarwald1147252012-05-08 13:52:30 -070043import android.view.MotionEvent;
Andy Stadler13d62042011-01-31 19:21:37 -080044import android.view.View;
45import android.view.View.OnClickListener;
Vikram Aggarwald1147252012-05-08 13:52:30 -070046import android.view.View.OnKeyListener;
47import android.view.View.OnTouchListener;
Jason parks8fd5bc92011-01-12 16:03:31 -060048import android.view.inputmethod.EditorInfo;
Ben Komalo9fcb6a72011-08-26 14:40:18 -070049import android.view.inputmethod.InputMethodInfo;
Jason parks75c085e2011-02-10 14:03:47 -060050import android.view.inputmethod.InputMethodManager;
Ben Komalo9fcb6a72011-08-26 14:40:18 -070051import android.view.inputmethod.InputMethodSubtype;
Andy Stadler13d62042011-01-31 19:21:37 -080052import android.widget.Button;
Jason parksec5a45e2011-01-18 15:28:36 -060053import android.widget.EditText;
54import android.widget.ProgressBar;
Jason parks8fd5bc92011-01-12 16:03:31 -060055import android.widget.TextView;
56
Ben Komalo91a2f052011-08-16 17:48:25 -070057import com.android.internal.telephony.ITelephony;
Vikram Aggarwalea1186d2012-05-03 15:35:03 -070058import com.android.internal.telephony.Phone;
Wink Saville55434042012-06-14 12:33:43 -070059import com.android.internal.telephony.PhoneConstants;
Ben Komalo9fcb6a72011-08-26 14:40:18 -070060
61import java.util.List;
Ben Komalo91a2f052011-08-16 17:48:25 -070062
63/**
64 * Settings screens to show the UI flows for encrypting/decrypting the device.
65 *
66 * This may be started via adb for debugging the UI layout, without having to go through
67 * encryption flows everytime. It should be noted that starting the activity in this manner
68 * is only useful for verifying UI-correctness - the behavior will not be identical.
69 * <pre>
70 * $ adb shell pm enable com.android.settings/.CryptKeeper
71 * $ adb shell am start \
72 * -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "progress" \
73 * -n com.android.settings/.CryptKeeper
74 * </pre>
75 */
Vikram Aggarwald1147252012-05-08 13:52:30 -070076public class CryptKeeper extends Activity implements TextView.OnEditorActionListener,
77 OnKeyListener, OnTouchListener, TextWatcher {
Jason parksec5a45e2011-01-18 15:28:36 -060078 private static final String TAG = "CryptKeeper";
Jason parks35933812011-01-21 15:48:20 -060079
Jason parks8fd5bc92011-01-12 16:03:31 -060080 private static final String DECRYPT_STATE = "trigger_restart_framework";
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -070081 /** Message sent to us to indicate encryption update progress. */
82 private static final int MESSAGE_UPDATE_PROGRESS = 1;
83 /** Message sent to us to cool-down (waste user's time between password attempts) */
84 private static final int MESSAGE_COOLDOWN = 2;
85 /** Message sent to us to indicate alerting the user that we are waiting for password entry */
86 private static final int MESSAGE_NOTIFY = 3;
Jason parksec5a45e2011-01-18 15:28:36 -060087
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -070088 // Constants used to control policy.
Jason parksec5a45e2011-01-18 15:28:36 -060089 private static final int MAX_FAILED_ATTEMPTS = 30;
90 private static final int COOL_DOWN_ATTEMPTS = 10;
91 private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
92
David Brown8373b452011-06-20 12:38:45 -070093 // Intent action for launching the Emergency Dialer activity.
94 static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
95
Ben Komalo91a2f052011-08-16 17:48:25 -070096 // Debug Intent extras so that this Activity may be started via adb for debugging UI layouts
97 private static final String EXTRA_FORCE_VIEW =
98 "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW";
99 private static final String FORCE_VIEW_PROGRESS = "progress";
Ben Komalo91a2f052011-08-16 17:48:25 -0700100 private static final String FORCE_VIEW_ERROR = "error";
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700101 private static final String FORCE_VIEW_PASSWORD = "password";
Ben Komalo91a2f052011-08-16 17:48:25 -0700102
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700103 /** When encryption is detected, this flag indicates whether or not we've checked for errors. */
Ben Komalo0e666092011-09-01 15:42:00 -0700104 private boolean mValidationComplete;
Ben Komalod4758ef2011-09-08 14:36:08 -0700105 private boolean mValidationRequested;
Ben Komalo0e666092011-09-01 15:42:00 -0700106 /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
107 private boolean mEncryptionGoneBad;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700108 /** A flag to indicate when the back event should be ignored */
109 private boolean mIgnoreBack = false;
Andy Stadler14997402011-02-01 15:34:59 -0800110 private int mCooldown;
111 PowerManager.WakeLock mWakeLock;
Jason parks06c5ff42011-03-01 10:17:40 -0600112 private EditText mPasswordEntry;
Vikram Aggarwald1147252012-05-08 13:52:30 -0700113 /** Number of calls to {@link #notifyUser()} to ignore before notifying. */
114 private int mNotificationCountdown = 0;
Andy Stadler14997402011-02-01 15:34:59 -0800115
116 /**
117 * Used to propagate state through configuration changes (e.g. screen rotation)
118 */
119 private static class NonConfigurationInstanceState {
120 final PowerManager.WakeLock wakelock;
121
122 NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
123 wakelock = _wakelock;
124 }
125 }
126
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700127 /**
128 * Activity used to fade the screen to black after the password is entered.
129 */
Vikram Aggarwal9f55ae22012-04-02 13:36:35 -0700130 public static class FadeToBlack extends Activity {
Andy Stadler13d62042011-01-31 19:21:37 -0800131 @Override
132 public void onCreate(Bundle savedInstanceState) {
133 super.onCreate(savedInstanceState);
134 setContentView(R.layout.crypt_keeper_blank);
135 }
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700136 /** Ignore all back events. */
137 @Override
138 public void onBackPressed() {
139 return;
140 }
Jason parksf1dbf552011-01-24 16:19:28 -0600141 }
Jason parksec5a45e2011-01-18 15:28:36 -0600142
Jason parks06c5ff42011-03-01 10:17:40 -0600143 private class DecryptTask extends AsyncTask<String, Void, Integer> {
144 @Override
145 protected Integer doInBackground(String... params) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700146 final IMountService service = getMountService();
Jason parks06c5ff42011-03-01 10:17:40 -0600147 try {
148 return service.decryptStorage(params[0]);
149 } catch (Exception e) {
150 Log.e(TAG, "Error while decrypting...", e);
151 return -1;
152 }
153 }
154
155 @Override
156 protected void onPostExecute(Integer failedAttempts) {
157 if (failedAttempts == 0) {
158 // The password was entered successfully. Start the Blank activity
159 // so this activity animates to black before the devices starts. Note
160 // It has 1 second to complete the animation or it will be frozen
161 // until the boot animation comes back up.
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700162 Intent intent = new Intent(CryptKeeper.this, FadeToBlack.class);
Jason parks06c5ff42011-03-01 10:17:40 -0600163 finish();
164 startActivity(intent);
165 } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
166 // Factory reset the device.
167 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
168 } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
169 mCooldown = COOL_DOWN_INTERVAL;
170 cooldown();
171 } else {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700172 final TextView status = (TextView) findViewById(R.id.status);
173 status.setText(R.string.try_again);
Jason parks06c5ff42011-03-01 10:17:40 -0600174 // Reenable the password entry
175 mPasswordEntry.setEnabled(true);
176 }
177 }
178 }
Jason parks75c085e2011-02-10 14:03:47 -0600179
Ben Komalo0e666092011-09-01 15:42:00 -0700180 private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
181 @Override
182 protected Boolean doInBackground(Void... params) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700183 final IMountService service = getMountService();
Ben Komalo0e666092011-09-01 15:42:00 -0700184 try {
Ben Komalod4758ef2011-09-08 14:36:08 -0700185 Log.d(TAG, "Validating encryption state.");
Ben Komalo0e666092011-09-01 15:42:00 -0700186 int state = service.getEncryptionState();
187 if (state == IMountService.ENCRYPTION_STATE_NONE) {
188 Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
189 return true; // Unexpected, but fine, I guess...
190 }
191 return state == IMountService.ENCRYPTION_STATE_OK;
192 } catch (RemoteException e) {
193 Log.w(TAG, "Unable to get encryption state properly");
194 return true;
195 }
196 }
197
198 @Override
199 protected void onPostExecute(Boolean result) {
200 mValidationComplete = true;
201 if (Boolean.FALSE.equals(result)) {
202 Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
203 mEncryptionGoneBad = true;
Ben Komalod4758ef2011-09-08 14:36:08 -0700204 } else {
205 Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
Ben Komalo0e666092011-09-01 15:42:00 -0700206 }
207 setupUi();
208 }
209 }
210
Ben Komalo91a2f052011-08-16 17:48:25 -0700211 private final Handler mHandler = new Handler() {
Jason parksec5a45e2011-01-18 15:28:36 -0600212 @Override
213 public void handleMessage(Message msg) {
Jason parksec5a45e2011-01-18 15:28:36 -0600214 switch (msg.what) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700215 case MESSAGE_UPDATE_PROGRESS:
Jason parksf8217302011-01-26 13:11:42 -0600216 updateProgress();
Jason parksec5a45e2011-01-18 15:28:36 -0600217 break;
Jason parks35933812011-01-21 15:48:20 -0600218
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700219 case MESSAGE_COOLDOWN:
Jason parksf8217302011-01-26 13:11:42 -0600220 cooldown();
Jason parksec5a45e2011-01-18 15:28:36 -0600221 break;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700222
223 case MESSAGE_NOTIFY:
224 notifyUser();
225 break;
Jason parksec5a45e2011-01-18 15:28:36 -0600226 }
227 }
228 };
Jason parks35933812011-01-21 15:48:20 -0600229
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700230 private AudioManager mAudioManager;
231
Ben Komalo91a2f052011-08-16 17:48:25 -0700232 /** @return whether or not this Activity was started for debugging the UI only. */
233 private boolean isDebugView() {
234 return getIntent().hasExtra(EXTRA_FORCE_VIEW);
235 }
236
237 /** @return whether or not this Activity was started for debugging the specific UI view only. */
238 private boolean isDebugView(String viewType /* non-nullable */) {
239 return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
240 }
241
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700242 /**
243 * Notify the user that we are awaiting input. Currently this sends an audio alert.
244 */
245 private void notifyUser() {
Vikram Aggarwald1147252012-05-08 13:52:30 -0700246 if (mNotificationCountdown > 0) {
247 Log.d(TAG, "Counting down to notify user..." + mNotificationCountdown);
248 --mNotificationCountdown;
249 } else if (mAudioManager != null) {
250 Log.d(TAG, "Notifying user that we are waiting for input...");
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700251 try {
252 // Play the standard keypress sound at full volume. This should be available on
253 // every device. We cannot play a ringtone here because media services aren't
254 // available yet. A DTMF-style tone is too soft to be noticed, and might not exist
255 // on tablet devices. The idea is to alert the user that something is needed: this
256 // does not have to be pleasing.
257 mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100);
258 } catch (Exception e) {
259 Log.w(TAG, "notifyUser: Exception while playing sound: " + e);
260 }
261 }
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700262 // Notify the user again in 5 seconds.
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700263 mHandler.removeMessages(MESSAGE_NOTIFY);
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700264 mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700265 }
266
267 /**
268 * Ignore back events after the user has entered the decrypt screen and while the device is
269 * encrypting.
270 */
271 @Override
272 public void onBackPressed() {
273 if (mIgnoreBack)
274 return;
275 super.onBackPressed();
276 }
277
Jason parks8fd5bc92011-01-12 16:03:31 -0600278 @Override
279 public void onCreate(Bundle savedInstanceState) {
280 super.onCreate(savedInstanceState);
Jason parks35933812011-01-21 15:48:20 -0600281
Andy Stadler95974062011-02-01 17:35:20 -0800282 // If we are not encrypted or encrypting, get out quickly.
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700283 final String state = SystemProperties.get("vold.decrypt");
Ben Komalo91a2f052011-08-16 17:48:25 -0700284 if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
Jason parks35933812011-01-21 15:48:20 -0600285 // Disable the crypt keeper.
Jason parks8fd5bc92011-01-12 16:03:31 -0600286 PackageManager pm = getPackageManager();
287 ComponentName name = new ComponentName(this, CryptKeeper.class);
Dianne Hackborn140f6c62011-10-16 11:49:00 -0700288 pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
289 PackageManager.DONT_KILL_APP);
290 // Typically CryptKeeper is launched as the home app. We didn't
Dianne Hackborn644fa422011-10-18 13:38:03 -0700291 // want to be running, so need to finish this activity. We can count
292 // on the activity manager re-launching the new home app upon finishing
293 // this one, since this will leave the activity stack empty.
Dianne Hackborn140f6c62011-10-16 11:49:00 -0700294 // NOTE: This is really grungy. I think it would be better for the
295 // activity manager to explicitly launch the crypt keeper instead of
296 // home in the situation where we need to decrypt the device
297 finish();
Jason parks8fd5bc92011-01-12 16:03:31 -0600298 return;
299 }
Jason parks35933812011-01-21 15:48:20 -0600300
Vikram Aggarwalb96b35a2012-05-01 11:09:39 -0700301 // Disable the status bar, but do NOT disable back because the user needs a way to go
302 // from keyboard settings and back to the password screen.
Jason parks39f1e042011-01-20 23:29:28 -0600303 StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
Andy Stadler13d62042011-01-31 19:21:37 -0800304 sbm.disable(StatusBarManager.DISABLE_EXPAND
305 | StatusBarManager.DISABLE_NOTIFICATION_ICONS
Jason parks39f1e042011-01-20 23:29:28 -0600306 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
Andy Stadler13d62042011-01-31 19:21:37 -0800307 | StatusBarManager.DISABLE_SYSTEM_INFO
Daniel Sandler4d2bfd12011-10-12 15:34:23 -0400308 | StatusBarManager.DISABLE_HOME
Vikram Aggarwalb96b35a2012-05-01 11:09:39 -0700309 | StatusBarManager.DISABLE_RECENT);
Andy Stadler14997402011-02-01 15:34:59 -0800310
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700311 setAirplaneModeIfNecessary();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700312 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Andy Stadler14997402011-02-01 15:34:59 -0800313 // Check for (and recover) retained instance data
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700314 final Object lastInstance = getLastNonConfigurationInstance();
Andy Stadler14997402011-02-01 15:34:59 -0800315 if (lastInstance instanceof NonConfigurationInstanceState) {
316 NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
317 mWakeLock = retained.wakelock;
Ben Komalo04606752011-08-18 14:50:26 -0700318 Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
Andy Stadler14997402011-02-01 15:34:59 -0800319 }
Jason parksec5a45e2011-01-18 15:28:36 -0600320 }
Jason parks35933812011-01-21 15:48:20 -0600321
Andy Stadler95974062011-02-01 17:35:20 -0800322 /**
323 * Note, we defer the state check and screen setup to onStart() because this will be
324 * re-run if the user clicks the power button (sleeping/waking the screen), and this is
325 * especially important if we were to lose the wakelock for any reason.
326 */
327 @Override
328 public void onStart() {
329 super.onStart();
Ben Komalod4758ef2011-09-08 14:36:08 -0700330 setupUi();
Ben Komalo0e666092011-09-01 15:42:00 -0700331 }
332
333 /**
334 * Initializes the UI based on the current state of encryption.
335 * This is idempotent - calling repeatedly will simply re-initialize the UI.
336 */
337 private void setupUi() {
338 if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
339 setContentView(R.layout.crypt_keeper_progress);
340 showFactoryReset();
341 return;
342 }
343
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700344 final String progress = SystemProperties.get("vold.encrypt_progress");
Ben Komalo0e666092011-09-01 15:42:00 -0700345 if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
Andy Stadler95974062011-02-01 17:35:20 -0800346 setContentView(R.layout.crypt_keeper_progress);
347 encryptionProgressInit();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700348 } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
Andy Stadler95974062011-02-01 17:35:20 -0800349 setContentView(R.layout.crypt_keeper_password_entry);
350 passwordEntryInit();
Ben Komalod4758ef2011-09-08 14:36:08 -0700351 } else if (!mValidationRequested) {
352 // We're supposed to be encrypted, but no validation has been done.
353 new ValidationTask().execute((Void[]) null);
354 mValidationRequested = true;
Andy Stadler95974062011-02-01 17:35:20 -0800355 }
356 }
357
Jason parksf8217302011-01-26 13:11:42 -0600358 @Override
359 public void onStop() {
360 super.onStop();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700361 mHandler.removeMessages(MESSAGE_COOLDOWN);
362 mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
363 mHandler.removeMessages(MESSAGE_NOTIFY);
Andy Stadler14997402011-02-01 15:34:59 -0800364 }
365
366 /**
367 * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
368 * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
369 * mWakeLock so the subsequent call to onDestroy does not release it.
370 */
371 @Override
372 public Object onRetainNonConfigurationInstance() {
373 NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
Ben Komalo04606752011-08-18 14:50:26 -0700374 Log.d(TAG, "Handing wakelock off to NonConfigurationInstanceState");
Andy Stadler14997402011-02-01 15:34:59 -0800375 mWakeLock = null;
376 return state;
377 }
378
379 @Override
380 public void onDestroy() {
381 super.onDestroy();
Jason parksf8217302011-01-26 13:11:42 -0600382
383 if (mWakeLock != null) {
Ben Komalo04606752011-08-18 14:50:26 -0700384 Log.d(TAG, "Releasing and destroying wakelock");
Jason parksf8217302011-01-26 13:11:42 -0600385 mWakeLock.release();
386 mWakeLock = null;
387 }
388 }
389
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700390 /**
391 * Start encrypting the device.
392 */
Jason parksec5a45e2011-01-18 15:28:36 -0600393 private void encryptionProgressInit() {
Jason parks35933812011-01-21 15:48:20 -0600394 // Accquire a partial wakelock to prevent the device from sleeping. Note
395 // we never release this wakelock as we will be restarted after the device
396 // is encrypted.
Ben Komalo04606752011-08-18 14:50:26 -0700397 Log.d(TAG, "Encryption progress screen initializing.");
Ben Komalo9ee164f2011-09-21 10:40:50 -0700398 if (mWakeLock == null) {
Ben Komalo04606752011-08-18 14:50:26 -0700399 Log.d(TAG, "Acquiring wakelock.");
400 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
401 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
402 mWakeLock.acquire();
403 }
Jason parks35933812011-01-21 15:48:20 -0600404
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700405 ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700406 // Ignore all back presses from now, both hard and soft keys.
407 mIgnoreBack = true;
408 // Start the first run of progress manually. This method sets up messages to occur at
409 // repeated intervals.
Jason parksf8217302011-01-26 13:11:42 -0600410 updateProgress();
411 }
412
Andy Stadler13d62042011-01-31 19:21:37 -0800413 private void showFactoryReset() {
414 // Hide the encryption-bot to make room for the "factory reset" button
415 findViewById(R.id.encroid).setVisibility(View.GONE);
416
417 // Show the reset button, failure text, and a divider
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700418 final Button button = (Button) findViewById(R.id.factory_reset);
Andy Stadler13d62042011-01-31 19:21:37 -0800419 button.setVisibility(View.VISIBLE);
420 button.setOnClickListener(new OnClickListener() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700421 @Override
Andy Stadler13d62042011-01-31 19:21:37 -0800422 public void onClick(View v) {
423 // Factory reset the device.
424 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
425 }
426 });
427
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700428 // Alert the user of the failure.
429 ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
430 ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
Andy Stadler13d62042011-01-31 19:21:37 -0800431
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700432 final View view = findViewById(R.id.bottom_divider);
433 // TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate.
Ben Komalof0104df2011-08-16 17:48:42 -0700434 if (view != null) {
435 view.setVisibility(View.VISIBLE);
436 }
Andy Stadler13d62042011-01-31 19:21:37 -0800437 }
438
Jason parksf8217302011-01-26 13:11:42 -0600439 private void updateProgress() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700440 final String state = SystemProperties.get("vold.encrypt_progress");
Jason parksf8217302011-01-26 13:11:42 -0600441
Ben Komalo0e666092011-09-01 15:42:00 -0700442 if ("error_partially_encrypted".equals(state)) {
Andy Stadler13d62042011-01-31 19:21:37 -0800443 showFactoryReset();
444 return;
445 }
446
Jason parksf8217302011-01-26 13:11:42 -0600447 int progress = 0;
448 try {
Ben Komalo91a2f052011-08-16 17:48:25 -0700449 // Force a 50% progress state when debugging the view.
450 progress = isDebugView() ? 50 : Integer.parseInt(state);
Jason parksf8217302011-01-26 13:11:42 -0600451 } catch (Exception e) {
452 Log.w(TAG, "Error parsing progress: " + e.toString());
453 }
454
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700455 final CharSequence status = getText(R.string.crypt_keeper_setup_description);
Ben Komalo04606752011-08-18 14:50:26 -0700456 Log.v(TAG, "Encryption progress: " + progress);
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700457 final TextView tv = (TextView) findViewById(R.id.status);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700458 if (tv != null) {
459 tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
460 }
Jason parksf8217302011-01-26 13:11:42 -0600461 // Check the progress every 5 seconds
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700462 mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
463 mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
Jason parksf8217302011-01-26 13:11:42 -0600464 }
465
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700466 /** Disable password input for a while to force the user to waste time between retries */
Jason parksf8217302011-01-26 13:11:42 -0600467 private void cooldown() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700468 final TextView status = (TextView) findViewById(R.id.status);
Andy Stadler13d62042011-01-31 19:21:37 -0800469
Jason parksf8217302011-01-26 13:11:42 -0600470 if (mCooldown <= 0) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700471 // Re-enable the password entry and back presses.
Jason parks06c5ff42011-03-01 10:17:40 -0600472 mPasswordEntry.setEnabled(true);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700473 mIgnoreBack = false;
Vikram Aggarwalf576dd62012-05-22 10:58:10 -0700474 status.setText(R.string.enter_password);
Jason parksf8217302011-01-26 13:11:42 -0600475 } else {
Andy Stadler13d62042011-01-31 19:21:37 -0800476 CharSequence template = getText(R.string.crypt_keeper_cooldown);
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700477 status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
Andy Stadler13d62042011-01-31 19:21:37 -0800478
Jason parksf8217302011-01-26 13:11:42 -0600479 mCooldown--;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700480 mHandler.removeMessages(MESSAGE_COOLDOWN);
481 mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second
Jason parksf8217302011-01-26 13:11:42 -0600482 }
Jason parksec5a45e2011-01-18 15:28:36 -0600483 }
Jason parks35933812011-01-21 15:48:20 -0600484
Jason parksec5a45e2011-01-18 15:28:36 -0600485 private void passwordEntryInit() {
Jason parks06c5ff42011-03-01 10:17:40 -0600486 mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
487 mPasswordEntry.setOnEditorActionListener(this);
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700488 mPasswordEntry.requestFocus();
Vikram Aggarwald1147252012-05-08 13:52:30 -0700489 // Become quiet when the user interacts with the Edit text screen.
490 mPasswordEntry.setOnKeyListener(this);
491 mPasswordEntry.setOnTouchListener(this);
492 mPasswordEntry.addTextChangedListener(this);
Jason parks35933812011-01-21 15:48:20 -0600493
Vikram Aggarwalc62d3212012-05-02 16:29:10 -0700494 // Disable the Emergency call button if the device has no voice telephone capability
495 final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
496 if (!tm.isVoiceCapable()) {
497 final View emergencyCall = findViewById(R.id.emergencyCallButton);
498 if (emergencyCall != null) {
499 Log.d(TAG, "Removing the emergency Call button");
500 emergencyCall.setVisibility(View.GONE);
501 }
502 }
503
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700504 final View imeSwitcher = findViewById(R.id.switch_ime_button);
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700505 final InputMethodManager imm = (InputMethodManager) getSystemService(
506 Context.INPUT_METHOD_SERVICE);
507 if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
508 imeSwitcher.setVisibility(View.VISIBLE);
509 imeSwitcher.setOnClickListener(new OnClickListener() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700510 @Override
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700511 public void onClick(View v) {
512 imm.showInputMethodPicker();
513 }
514 });
Jason parks00046d62011-06-13 17:38:45 -0500515 }
David Brown8373b452011-06-20 12:38:45 -0700516
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700517 // We want to keep the screen on while waiting for input. In minimal boot mode, the device
518 // is completely non-functional, and we want the user to notice the device and enter a
519 // password.
520 if (mWakeLock == null) {
521 Log.d(TAG, "Acquiring wakelock.");
522 final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
523 if (pm != null) {
524 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
525 mWakeLock.acquire();
526 }
527 }
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700528 // Asynchronously throw up the IME, since there are issues with requesting it to be shown
529 // immediately.
530 mHandler.postDelayed(new Runnable() {
531 @Override public void run() {
532 imm.showSoftInputUnchecked(0, null);
533 }
534 }, 0);
535
David Brown8373b452011-06-20 12:38:45 -0700536 updateEmergencyCallButtonState();
Vikram Aggarwald1147252012-05-08 13:52:30 -0700537 // Notify the user in 120 seconds that we are waiting for him to enter the password.
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700538 mHandler.removeMessages(MESSAGE_NOTIFY);
Vikram Aggarwald1147252012-05-08 13:52:30 -0700539 mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);
Jason parks8fd5bc92011-01-12 16:03:31 -0600540 }
541
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700542 /**
543 * Method adapted from com.android.inputmethod.latin.Utils
544 *
545 * @param imm The input method manager
546 * @param shouldIncludeAuxiliarySubtypes
547 * @return true if we have multiple IMEs to choose from
548 */
549 private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
550 final boolean shouldIncludeAuxiliarySubtypes) {
551 final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
552
553 // Number of the filtered IMEs
554 int filteredImisCount = 0;
555
556 for (InputMethodInfo imi : enabledImis) {
557 // We can return true immediately after we find two or more filtered IMEs.
558 if (filteredImisCount > 1) return true;
559 final List<InputMethodSubtype> subtypes =
560 imm.getEnabledInputMethodSubtypeList(imi, true);
561 // IMEs that have no subtypes should be counted.
562 if (subtypes.isEmpty()) {
563 ++filteredImisCount;
564 continue;
565 }
566
567 int auxCount = 0;
568 for (InputMethodSubtype subtype : subtypes) {
569 if (subtype.isAuxiliary()) {
570 ++auxCount;
571 }
572 }
573 final int nonAuxCount = subtypes.size() - auxCount;
574
575 // IMEs that have one or more non-auxiliary subtypes should be counted.
576 // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
577 // subtypes should be counted as well.
578 if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
579 ++filteredImisCount;
580 continue;
581 }
582 }
583
584 return filteredImisCount > 1
585 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
586 // input method subtype (The current IME should be LatinIME.)
587 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
588 }
589
Jason parks8fd5bc92011-01-12 16:03:31 -0600590 private IMountService getMountService() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700591 final IBinder service = ServiceManager.getService("mount");
Jason parks8fd5bc92011-01-12 16:03:31 -0600592 if (service != null) {
593 return IMountService.Stub.asInterface(service);
594 }
595 return null;
596 }
597
598 @Override
599 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Jason parks00046d62011-06-13 17:38:45 -0500600 if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
Jason parks8fd5bc92011-01-12 16:03:31 -0600601 // Get the password
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700602 final String password = v.getText().toString();
Jason parks8fd5bc92011-01-12 16:03:31 -0600603
Jason parksec5a45e2011-01-18 15:28:36 -0600604 if (TextUtils.isEmpty(password)) {
605 return true;
606 }
Jason parks35933812011-01-21 15:48:20 -0600607
Jason parks8fd5bc92011-01-12 16:03:31 -0600608 // Now that we have the password clear the password field.
609 v.setText(null);
610
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700611 // Disable the password entry and back keypress while checking the password. These
612 // we either be re-enabled if the password was wrong or after the cooldown period.
Jason parks06c5ff42011-03-01 10:17:40 -0600613 mPasswordEntry.setEnabled(false);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700614 mIgnoreBack = true;
Jason parks8fd5bc92011-01-12 16:03:31 -0600615
Ben Komalo04606752011-08-18 14:50:26 -0700616 Log.d(TAG, "Attempting to send command to decrypt");
Jason parks06c5ff42011-03-01 10:17:40 -0600617 new DecryptTask().execute(password);
Jason parks35933812011-01-21 15:48:20 -0600618
Jason parks8fd5bc92011-01-12 16:03:31 -0600619 return true;
620 }
621 return false;
622 }
David Brown8373b452011-06-20 12:38:45 -0700623
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700624 /**
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700625 * Set airplane mode on the device if it isn't an LTE device.
626 * Full story: In minimal boot mode, we cannot save any state. In particular, we cannot save
627 * any incoming SMS's. So SMSs that are received here will be silently dropped to the floor.
628 * That is bad. Also, we cannot receive any telephone calls in this state. So to avoid
629 * both these problems, we turn the radio off. However, on certain networks turning on and
630 * off the radio takes a long time. In such cases, we are better off leaving the radio
631 * running so the latency of an E911 call is short.
632 * The behavior after this is:
633 * 1. Emergency dialing: the emergency dialer has logic to force the device out of
634 * airplane mode and restart the radio.
635 * 2. Full boot: we read the persistent settings from the previous boot and restore the
636 * radio to whatever it was before it restarted. This also happens when rebooting a
637 * phone that has no encryption.
638 */
639 private final void setAirplaneModeIfNecessary() {
640 final boolean isLteDevice =
Wink Saville55434042012-06-14 12:33:43 -0700641 TelephonyManager.getDefault().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700642 if (!isLteDevice) {
643 Log.d(TAG, "Going into airplane mode.");
Vikram Aggarwal6ebbd302012-05-04 14:09:52 -0700644 Settings.System.putInt(getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 1);
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700645 final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
646 intent.putExtra("state", true);
647 sendBroadcast(intent);
648 }
649 }
650
651 /**
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700652 * Code to update the state of, and handle clicks from, the "Emergency call" button.
653 *
654 * This code is mostly duplicated from the corresponding code in
655 * LockPatternUtils and LockPatternKeyguardView under frameworks/base.
656 */
David Brown8373b452011-06-20 12:38:45 -0700657 private void updateEmergencyCallButtonState() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700658 final Button emergencyCall = (Button) findViewById(R.id.emergencyCallButton);
David Brown8373b452011-06-20 12:38:45 -0700659 // The button isn't present at all in some configurations.
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700660 if (emergencyCall == null)
661 return;
David Brown8373b452011-06-20 12:38:45 -0700662
663 if (isEmergencyCallCapable()) {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700664 emergencyCall.setVisibility(View.VISIBLE);
665 emergencyCall.setOnClickListener(new View.OnClickListener() {
666 @Override
667
David Brown8373b452011-06-20 12:38:45 -0700668 public void onClick(View v) {
669 takeEmergencyCallAction();
670 }
671 });
672 } else {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700673 emergencyCall.setVisibility(View.GONE);
David Brown8373b452011-06-20 12:38:45 -0700674 return;
675 }
676
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700677 final int newState = TelephonyManager.getDefault().getCallState();
David Brown8373b452011-06-20 12:38:45 -0700678 int textId;
679 if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700680 // Show "return to call" text and show phone icon
David Brown8373b452011-06-20 12:38:45 -0700681 textId = R.string.cryptkeeper_return_to_call;
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700682 final int phoneCallIcon = R.drawable.stat_sys_phone_call;
683 emergencyCall.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
David Brown8373b452011-06-20 12:38:45 -0700684 } else {
685 textId = R.string.cryptkeeper_emergency_call;
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700686 final int emergencyIcon = R.drawable.ic_emergency;
687 emergencyCall.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
David Brown8373b452011-06-20 12:38:45 -0700688 }
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700689 emergencyCall.setText(textId);
David Brown8373b452011-06-20 12:38:45 -0700690 }
691
692 private boolean isEmergencyCallCapable() {
693 return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
694 }
695
696 private void takeEmergencyCallAction() {
697 if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
698 resumeCall();
699 } else {
700 launchEmergencyDialer();
701 }
702 }
703
704 private void resumeCall() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700705 final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
David Brown8373b452011-06-20 12:38:45 -0700706 if (phone != null) {
707 try {
708 phone.showCallScreen();
709 } catch (RemoteException e) {
710 Log.e(TAG, "Error calling ITelephony service: " + e);
711 }
712 }
713 }
714
715 private void launchEmergencyDialer() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700716 final Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
David Brown8373b452011-06-20 12:38:45 -0700717 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
718 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
719 startActivity(intent);
720 }
Vikram Aggarwald1147252012-05-08 13:52:30 -0700721
722 /**
723 * Listen to key events so we can disable sounds when we get a keyinput in EditText.
724 */
725 private void delayAudioNotification() {
726 Log.d(TAG, "User entering password: delay audio notification");
727 mNotificationCountdown = 20;
728 }
729
730 @Override
731 public boolean onKey(View v, int keyCode, KeyEvent event) {
732 delayAudioNotification();
733 return false;
734 }
735
736 @Override
737 public boolean onTouch(View v, MotionEvent event) {
738 delayAudioNotification();
739 return false;
740 }
741
742 @Override
743 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
744 return;
745 }
746
747 @Override
748 public void onTextChanged(CharSequence s, int start, int before, int count) {
749 delayAudioNotification();
750 }
751
752 @Override
753 public void afterTextChanged(Editable s) {
754 return;
755 }
David Brown8373b452011-06-20 12:38:45 -0700756}