blob: 1cb12f3f83b56eb4f69771b721dbddd57e58c276 [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;
Ben Komalo9fcb6a72011-08-26 14:40:18 -070059
60import java.util.List;
Ben Komalo91a2f052011-08-16 17:48:25 -070061
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 */
Vikram Aggarwald1147252012-05-08 13:52:30 -070075public class CryptKeeper extends Activity implements TextView.OnEditorActionListener,
76 OnKeyListener, OnTouchListener, TextWatcher {
Jason parksec5a45e2011-01-18 15:28:36 -060077 private static final String TAG = "CryptKeeper";
Jason parks35933812011-01-21 15:48:20 -060078
Jason parks8fd5bc92011-01-12 16:03:31 -060079 private static final String DECRYPT_STATE = "trigger_restart_framework";
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -070080 /** 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;
Jason parksec5a45e2011-01-18 15:28:36 -060086
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -070087 // Constants used to control policy.
Jason parksec5a45e2011-01-18 15:28:36 -060088 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
David Brown8373b452011-06-20 12:38:45 -070092 // Intent action for launching the Emergency Dialer activity.
93 static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
94
Ben Komalo91a2f052011-08-16 17:48:25 -070095 // 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";
Ben Komalo91a2f052011-08-16 17:48:25 -070099 private static final String FORCE_VIEW_ERROR = "error";
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700100 private static final String FORCE_VIEW_PASSWORD = "password";
Ben Komalo91a2f052011-08-16 17:48:25 -0700101
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700102 /** When encryption is detected, this flag indicates whether or not we've checked for errors. */
Ben Komalo0e666092011-09-01 15:42:00 -0700103 private boolean mValidationComplete;
Ben Komalod4758ef2011-09-08 14:36:08 -0700104 private boolean mValidationRequested;
Ben Komalo0e666092011-09-01 15:42:00 -0700105 /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
106 private boolean mEncryptionGoneBad;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700107 /** A flag to indicate when the back event should be ignored */
108 private boolean mIgnoreBack = false;
Andy Stadler14997402011-02-01 15:34:59 -0800109 private int mCooldown;
110 PowerManager.WakeLock mWakeLock;
Jason parks06c5ff42011-03-01 10:17:40 -0600111 private EditText mPasswordEntry;
Vikram Aggarwald1147252012-05-08 13:52:30 -0700112 /** Number of calls to {@link #notifyUser()} to ignore before notifying. */
113 private int mNotificationCountdown = 0;
Andy Stadler14997402011-02-01 15:34:59 -0800114
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
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700126 /**
127 * Activity used to fade the screen to black after the password is entered.
128 */
Vikram Aggarwal9f55ae22012-04-02 13:36:35 -0700129 public static class FadeToBlack extends Activity {
Andy Stadler13d62042011-01-31 19:21:37 -0800130 @Override
131 public void onCreate(Bundle savedInstanceState) {
132 super.onCreate(savedInstanceState);
133 setContentView(R.layout.crypt_keeper_blank);
134 }
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700135 /** Ignore all back events. */
136 @Override
137 public void onBackPressed() {
138 return;
139 }
Jason parksf1dbf552011-01-24 16:19:28 -0600140 }
Jason parksec5a45e2011-01-18 15:28:36 -0600141
Jason parks06c5ff42011-03-01 10:17:40 -0600142 private class DecryptTask extends AsyncTask<String, Void, Integer> {
143 @Override
144 protected Integer doInBackground(String... params) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700145 final IMountService service = getMountService();
Jason parks06c5ff42011-03-01 10:17:40 -0600146 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.
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700161 Intent intent = new Intent(CryptKeeper.this, FadeToBlack.class);
Jason parks06c5ff42011-03-01 10:17:40 -0600162 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 {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700171 final TextView status = (TextView) findViewById(R.id.status);
172 status.setText(R.string.try_again);
173 status.setVisibility(View.VISIBLE);
Jason parks06c5ff42011-03-01 10:17:40 -0600174
175 // Reenable the password entry
176 mPasswordEntry.setEnabled(true);
177 }
178 }
179 }
Jason parks75c085e2011-02-10 14:03:47 -0600180
Ben Komalo0e666092011-09-01 15:42:00 -0700181 private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
182 @Override
183 protected Boolean doInBackground(Void... params) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700184 final IMountService service = getMountService();
Ben Komalo0e666092011-09-01 15:42:00 -0700185 try {
Ben Komalod4758ef2011-09-08 14:36:08 -0700186 Log.d(TAG, "Validating encryption state.");
Ben Komalo0e666092011-09-01 15:42:00 -0700187 int state = service.getEncryptionState();
188 if (state == IMountService.ENCRYPTION_STATE_NONE) {
189 Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
190 return true; // Unexpected, but fine, I guess...
191 }
192 return state == IMountService.ENCRYPTION_STATE_OK;
193 } catch (RemoteException e) {
194 Log.w(TAG, "Unable to get encryption state properly");
195 return true;
196 }
197 }
198
199 @Override
200 protected void onPostExecute(Boolean result) {
201 mValidationComplete = true;
202 if (Boolean.FALSE.equals(result)) {
203 Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
204 mEncryptionGoneBad = true;
Ben Komalod4758ef2011-09-08 14:36:08 -0700205 } else {
206 Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
Ben Komalo0e666092011-09-01 15:42:00 -0700207 }
208 setupUi();
209 }
210 }
211
Ben Komalo91a2f052011-08-16 17:48:25 -0700212 private final Handler mHandler = new Handler() {
Jason parksec5a45e2011-01-18 15:28:36 -0600213 @Override
214 public void handleMessage(Message msg) {
Jason parksec5a45e2011-01-18 15:28:36 -0600215 switch (msg.what) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700216 case MESSAGE_UPDATE_PROGRESS:
Jason parksf8217302011-01-26 13:11:42 -0600217 updateProgress();
Jason parksec5a45e2011-01-18 15:28:36 -0600218 break;
Jason parks35933812011-01-21 15:48:20 -0600219
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700220 case MESSAGE_COOLDOWN:
Jason parksf8217302011-01-26 13:11:42 -0600221 cooldown();
Jason parksec5a45e2011-01-18 15:28:36 -0600222 break;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700223
224 case MESSAGE_NOTIFY:
225 notifyUser();
226 break;
Jason parksec5a45e2011-01-18 15:28:36 -0600227 }
228 }
229 };
Jason parks35933812011-01-21 15:48:20 -0600230
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700231 private AudioManager mAudioManager;
232
Ben Komalo91a2f052011-08-16 17:48:25 -0700233 /** @return whether or not this Activity was started for debugging the UI only. */
234 private boolean isDebugView() {
235 return getIntent().hasExtra(EXTRA_FORCE_VIEW);
236 }
237
238 /** @return whether or not this Activity was started for debugging the specific UI view only. */
239 private boolean isDebugView(String viewType /* non-nullable */) {
240 return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
241 }
242
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700243 /**
244 * Notify the user that we are awaiting input. Currently this sends an audio alert.
245 */
246 private void notifyUser() {
Vikram Aggarwald1147252012-05-08 13:52:30 -0700247 if (mNotificationCountdown > 0) {
248 Log.d(TAG, "Counting down to notify user..." + mNotificationCountdown);
249 --mNotificationCountdown;
250 } else if (mAudioManager != null) {
251 Log.d(TAG, "Notifying user that we are waiting for input...");
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700252 try {
253 // Play the standard keypress sound at full volume. This should be available on
254 // every device. We cannot play a ringtone here because media services aren't
255 // available yet. A DTMF-style tone is too soft to be noticed, and might not exist
256 // on tablet devices. The idea is to alert the user that something is needed: this
257 // does not have to be pleasing.
258 mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100);
259 } catch (Exception e) {
260 Log.w(TAG, "notifyUser: Exception while playing sound: " + e);
261 }
262 }
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700263 // Notify the user again in 5 seconds.
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700264 mHandler.removeMessages(MESSAGE_NOTIFY);
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700265 mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700266 }
267
268 /**
269 * Ignore back events after the user has entered the decrypt screen and while the device is
270 * encrypting.
271 */
272 @Override
273 public void onBackPressed() {
274 if (mIgnoreBack)
275 return;
276 super.onBackPressed();
277 }
278
Jason parks8fd5bc92011-01-12 16:03:31 -0600279 @Override
280 public void onCreate(Bundle savedInstanceState) {
281 super.onCreate(savedInstanceState);
Jason parks35933812011-01-21 15:48:20 -0600282
Andy Stadler95974062011-02-01 17:35:20 -0800283 // If we are not encrypted or encrypting, get out quickly.
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700284 final String state = SystemProperties.get("vold.decrypt");
Ben Komalo91a2f052011-08-16 17:48:25 -0700285 if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
Jason parks35933812011-01-21 15:48:20 -0600286 // Disable the crypt keeper.
Jason parks8fd5bc92011-01-12 16:03:31 -0600287 PackageManager pm = getPackageManager();
288 ComponentName name = new ComponentName(this, CryptKeeper.class);
Dianne Hackborn140f6c62011-10-16 11:49:00 -0700289 pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
290 PackageManager.DONT_KILL_APP);
291 // Typically CryptKeeper is launched as the home app. We didn't
Dianne Hackborn644fa422011-10-18 13:38:03 -0700292 // want to be running, so need to finish this activity. We can count
293 // on the activity manager re-launching the new home app upon finishing
294 // this one, since this will leave the activity stack empty.
Dianne Hackborn140f6c62011-10-16 11:49:00 -0700295 // NOTE: This is really grungy. I think it would be better for the
296 // activity manager to explicitly launch the crypt keeper instead of
297 // home in the situation where we need to decrypt the device
298 finish();
Jason parks8fd5bc92011-01-12 16:03:31 -0600299 return;
300 }
Jason parks35933812011-01-21 15:48:20 -0600301
Vikram Aggarwalb96b35a2012-05-01 11:09:39 -0700302 // Disable the status bar, but do NOT disable back because the user needs a way to go
303 // from keyboard settings and back to the password screen.
Jason parks39f1e042011-01-20 23:29:28 -0600304 StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
Andy Stadler13d62042011-01-31 19:21:37 -0800305 sbm.disable(StatusBarManager.DISABLE_EXPAND
306 | StatusBarManager.DISABLE_NOTIFICATION_ICONS
Jason parks39f1e042011-01-20 23:29:28 -0600307 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
Andy Stadler13d62042011-01-31 19:21:37 -0800308 | StatusBarManager.DISABLE_SYSTEM_INFO
Daniel Sandler4d2bfd12011-10-12 15:34:23 -0400309 | StatusBarManager.DISABLE_HOME
Vikram Aggarwalb96b35a2012-05-01 11:09:39 -0700310 | StatusBarManager.DISABLE_RECENT);
Andy Stadler14997402011-02-01 15:34:59 -0800311
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700312 setAirplaneModeIfNecessary();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700313 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
Andy Stadler14997402011-02-01 15:34:59 -0800314 // Check for (and recover) retained instance data
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700315 final Object lastInstance = getLastNonConfigurationInstance();
Andy Stadler14997402011-02-01 15:34:59 -0800316 if (lastInstance instanceof NonConfigurationInstanceState) {
317 NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
318 mWakeLock = retained.wakelock;
Ben Komalo04606752011-08-18 14:50:26 -0700319 Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
Andy Stadler14997402011-02-01 15:34:59 -0800320 }
Jason parksec5a45e2011-01-18 15:28:36 -0600321 }
Jason parks35933812011-01-21 15:48:20 -0600322
Andy Stadler95974062011-02-01 17:35:20 -0800323 /**
324 * Note, we defer the state check and screen setup to onStart() because this will be
325 * re-run if the user clicks the power button (sleeping/waking the screen), and this is
326 * especially important if we were to lose the wakelock for any reason.
327 */
328 @Override
329 public void onStart() {
330 super.onStart();
Ben Komalod4758ef2011-09-08 14:36:08 -0700331 setupUi();
Ben Komalo0e666092011-09-01 15:42:00 -0700332 }
333
334 /**
335 * Initializes the UI based on the current state of encryption.
336 * This is idempotent - calling repeatedly will simply re-initialize the UI.
337 */
338 private void setupUi() {
339 if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
340 setContentView(R.layout.crypt_keeper_progress);
341 showFactoryReset();
342 return;
343 }
344
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700345 final String progress = SystemProperties.get("vold.encrypt_progress");
Ben Komalo0e666092011-09-01 15:42:00 -0700346 if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
Andy Stadler95974062011-02-01 17:35:20 -0800347 setContentView(R.layout.crypt_keeper_progress);
348 encryptionProgressInit();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700349 } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
Andy Stadler95974062011-02-01 17:35:20 -0800350 setContentView(R.layout.crypt_keeper_password_entry);
351 passwordEntryInit();
Ben Komalod4758ef2011-09-08 14:36:08 -0700352 } else if (!mValidationRequested) {
353 // We're supposed to be encrypted, but no validation has been done.
354 new ValidationTask().execute((Void[]) null);
355 mValidationRequested = true;
Andy Stadler95974062011-02-01 17:35:20 -0800356 }
357 }
358
Jason parksf8217302011-01-26 13:11:42 -0600359 @Override
360 public void onStop() {
361 super.onStop();
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700362 mHandler.removeMessages(MESSAGE_COOLDOWN);
363 mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
364 mHandler.removeMessages(MESSAGE_NOTIFY);
Andy Stadler14997402011-02-01 15:34:59 -0800365 }
366
367 /**
368 * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
369 * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
370 * mWakeLock so the subsequent call to onDestroy does not release it.
371 */
372 @Override
373 public Object onRetainNonConfigurationInstance() {
374 NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
Ben Komalo04606752011-08-18 14:50:26 -0700375 Log.d(TAG, "Handing wakelock off to NonConfigurationInstanceState");
Andy Stadler14997402011-02-01 15:34:59 -0800376 mWakeLock = null;
377 return state;
378 }
379
380 @Override
381 public void onDestroy() {
382 super.onDestroy();
Jason parksf8217302011-01-26 13:11:42 -0600383
384 if (mWakeLock != null) {
Ben Komalo04606752011-08-18 14:50:26 -0700385 Log.d(TAG, "Releasing and destroying wakelock");
Jason parksf8217302011-01-26 13:11:42 -0600386 mWakeLock.release();
387 mWakeLock = null;
388 }
389 }
390
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700391 /**
392 * Start encrypting the device.
393 */
Jason parksec5a45e2011-01-18 15:28:36 -0600394 private void encryptionProgressInit() {
Jason parks35933812011-01-21 15:48:20 -0600395 // Accquire a partial wakelock to prevent the device from sleeping. Note
396 // we never release this wakelock as we will be restarted after the device
397 // is encrypted.
Ben Komalo04606752011-08-18 14:50:26 -0700398 Log.d(TAG, "Encryption progress screen initializing.");
Ben Komalo9ee164f2011-09-21 10:40:50 -0700399 if (mWakeLock == null) {
Ben Komalo04606752011-08-18 14:50:26 -0700400 Log.d(TAG, "Acquiring wakelock.");
401 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
402 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
403 mWakeLock.acquire();
404 }
Jason parks35933812011-01-21 15:48:20 -0600405
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700406 ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700407 // Ignore all back presses from now, both hard and soft keys.
408 mIgnoreBack = true;
409 // Start the first run of progress manually. This method sets up messages to occur at
410 // repeated intervals.
Jason parksf8217302011-01-26 13:11:42 -0600411 updateProgress();
412 }
413
Andy Stadler13d62042011-01-31 19:21:37 -0800414 private void showFactoryReset() {
415 // Hide the encryption-bot to make room for the "factory reset" button
416 findViewById(R.id.encroid).setVisibility(View.GONE);
417
418 // Show the reset button, failure text, and a divider
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700419 final Button button = (Button) findViewById(R.id.factory_reset);
Andy Stadler13d62042011-01-31 19:21:37 -0800420 button.setVisibility(View.VISIBLE);
421 button.setOnClickListener(new OnClickListener() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700422 @Override
Andy Stadler13d62042011-01-31 19:21:37 -0800423 public void onClick(View v) {
424 // Factory reset the device.
425 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
426 }
427 });
428
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700429 // Alert the user of the failure.
430 ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
431 ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
Andy Stadler13d62042011-01-31 19:21:37 -0800432
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700433 final View view = findViewById(R.id.bottom_divider);
434 // TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate.
Ben Komalof0104df2011-08-16 17:48:42 -0700435 if (view != null) {
436 view.setVisibility(View.VISIBLE);
437 }
Andy Stadler13d62042011-01-31 19:21:37 -0800438 }
439
Jason parksf8217302011-01-26 13:11:42 -0600440 private void updateProgress() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700441 final String state = SystemProperties.get("vold.encrypt_progress");
Jason parksf8217302011-01-26 13:11:42 -0600442
Ben Komalo0e666092011-09-01 15:42:00 -0700443 if ("error_partially_encrypted".equals(state)) {
Andy Stadler13d62042011-01-31 19:21:37 -0800444 showFactoryReset();
445 return;
446 }
447
Jason parksf8217302011-01-26 13:11:42 -0600448 int progress = 0;
449 try {
Ben Komalo91a2f052011-08-16 17:48:25 -0700450 // Force a 50% progress state when debugging the view.
451 progress = isDebugView() ? 50 : Integer.parseInt(state);
Jason parksf8217302011-01-26 13:11:42 -0600452 } catch (Exception e) {
453 Log.w(TAG, "Error parsing progress: " + e.toString());
454 }
455
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700456 final CharSequence status = getText(R.string.crypt_keeper_setup_description);
Ben Komalo04606752011-08-18 14:50:26 -0700457 Log.v(TAG, "Encryption progress: " + progress);
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700458 final TextView tv = (TextView) findViewById(R.id.status);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700459 if (tv != null) {
460 tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
461 }
Jason parksf8217302011-01-26 13:11:42 -0600462 // Check the progress every 5 seconds
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700463 mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
464 mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
Jason parksf8217302011-01-26 13:11:42 -0600465 }
466
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700467 /** Disable password input for a while to force the user to waste time between retries */
Jason parksf8217302011-01-26 13:11:42 -0600468 private void cooldown() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700469 final TextView status = (TextView) findViewById(R.id.status);
Andy Stadler13d62042011-01-31 19:21:37 -0800470
Jason parksf8217302011-01-26 13:11:42 -0600471 if (mCooldown <= 0) {
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700472 // Re-enable the password entry and back presses.
Jason parks06c5ff42011-03-01 10:17:40 -0600473 mPasswordEntry.setEnabled(true);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700474 mIgnoreBack = false;
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700475 status.setVisibility(View.GONE);
Jason parksf8217302011-01-26 13:11:42 -0600476 } else {
Andy Stadler13d62042011-01-31 19:21:37 -0800477 CharSequence template = getText(R.string.crypt_keeper_cooldown);
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700478 status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
Andy Stadler13d62042011-01-31 19:21:37 -0800479
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700480 status.setVisibility(View.VISIBLE);
Jason parksf8217302011-01-26 13:11:42 -0600481
482 mCooldown--;
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700483 mHandler.removeMessages(MESSAGE_COOLDOWN);
484 mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second
Jason parksf8217302011-01-26 13:11:42 -0600485 }
Jason parksec5a45e2011-01-18 15:28:36 -0600486 }
Jason parks35933812011-01-21 15:48:20 -0600487
Jason parksec5a45e2011-01-18 15:28:36 -0600488 private void passwordEntryInit() {
Jason parks06c5ff42011-03-01 10:17:40 -0600489 mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
490 mPasswordEntry.setOnEditorActionListener(this);
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700491 mPasswordEntry.requestFocus();
Vikram Aggarwald1147252012-05-08 13:52:30 -0700492 // Become quiet when the user interacts with the Edit text screen.
493 mPasswordEntry.setOnKeyListener(this);
494 mPasswordEntry.setOnTouchListener(this);
495 mPasswordEntry.addTextChangedListener(this);
Jason parks35933812011-01-21 15:48:20 -0600496
Vikram Aggarwalc62d3212012-05-02 16:29:10 -0700497 // Disable the Emergency call button if the device has no voice telephone capability
498 final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
499 if (!tm.isVoiceCapable()) {
500 final View emergencyCall = findViewById(R.id.emergencyCallButton);
501 if (emergencyCall != null) {
502 Log.d(TAG, "Removing the emergency Call button");
503 emergencyCall.setVisibility(View.GONE);
504 }
505 }
506
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700507 final View imeSwitcher = findViewById(R.id.switch_ime_button);
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700508 final InputMethodManager imm = (InputMethodManager) getSystemService(
509 Context.INPUT_METHOD_SERVICE);
510 if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
511 imeSwitcher.setVisibility(View.VISIBLE);
512 imeSwitcher.setOnClickListener(new OnClickListener() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700513 @Override
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700514 public void onClick(View v) {
515 imm.showInputMethodPicker();
516 }
517 });
Jason parks00046d62011-06-13 17:38:45 -0500518 }
David Brown8373b452011-06-20 12:38:45 -0700519
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700520 // We want to keep the screen on while waiting for input. In minimal boot mode, the device
521 // is completely non-functional, and we want the user to notice the device and enter a
522 // password.
523 if (mWakeLock == null) {
524 Log.d(TAG, "Acquiring wakelock.");
525 final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
526 if (pm != null) {
527 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
528 mWakeLock.acquire();
529 }
530 }
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700531 // Asynchronously throw up the IME, since there are issues with requesting it to be shown
532 // immediately.
533 mHandler.postDelayed(new Runnable() {
534 @Override public void run() {
535 imm.showSoftInputUnchecked(0, null);
536 }
537 }, 0);
538
David Brown8373b452011-06-20 12:38:45 -0700539 updateEmergencyCallButtonState();
Vikram Aggarwald1147252012-05-08 13:52:30 -0700540 // Notify the user in 120 seconds that we are waiting for him to enter the password.
Vikram Aggarwal86b93932012-05-01 16:46:06 -0700541 mHandler.removeMessages(MESSAGE_NOTIFY);
Vikram Aggarwald1147252012-05-08 13:52:30 -0700542 mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);
Jason parks8fd5bc92011-01-12 16:03:31 -0600543 }
544
Ben Komalo9fcb6a72011-08-26 14:40:18 -0700545 /**
546 * Method adapted from com.android.inputmethod.latin.Utils
547 *
548 * @param imm The input method manager
549 * @param shouldIncludeAuxiliarySubtypes
550 * @return true if we have multiple IMEs to choose from
551 */
552 private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
553 final boolean shouldIncludeAuxiliarySubtypes) {
554 final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
555
556 // Number of the filtered IMEs
557 int filteredImisCount = 0;
558
559 for (InputMethodInfo imi : enabledImis) {
560 // We can return true immediately after we find two or more filtered IMEs.
561 if (filteredImisCount > 1) return true;
562 final List<InputMethodSubtype> subtypes =
563 imm.getEnabledInputMethodSubtypeList(imi, true);
564 // IMEs that have no subtypes should be counted.
565 if (subtypes.isEmpty()) {
566 ++filteredImisCount;
567 continue;
568 }
569
570 int auxCount = 0;
571 for (InputMethodSubtype subtype : subtypes) {
572 if (subtype.isAuxiliary()) {
573 ++auxCount;
574 }
575 }
576 final int nonAuxCount = subtypes.size() - auxCount;
577
578 // IMEs that have one or more non-auxiliary subtypes should be counted.
579 // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
580 // subtypes should be counted as well.
581 if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
582 ++filteredImisCount;
583 continue;
584 }
585 }
586
587 return filteredImisCount > 1
588 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
589 // input method subtype (The current IME should be LatinIME.)
590 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
591 }
592
Jason parks8fd5bc92011-01-12 16:03:31 -0600593 private IMountService getMountService() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700594 final IBinder service = ServiceManager.getService("mount");
Jason parks8fd5bc92011-01-12 16:03:31 -0600595 if (service != null) {
596 return IMountService.Stub.asInterface(service);
597 }
598 return null;
599 }
600
601 @Override
602 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Jason parks00046d62011-06-13 17:38:45 -0500603 if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
Jason parks8fd5bc92011-01-12 16:03:31 -0600604 // Get the password
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700605 final String password = v.getText().toString();
Jason parks8fd5bc92011-01-12 16:03:31 -0600606
Jason parksec5a45e2011-01-18 15:28:36 -0600607 if (TextUtils.isEmpty(password)) {
608 return true;
609 }
Jason parks35933812011-01-21 15:48:20 -0600610
Jason parks8fd5bc92011-01-12 16:03:31 -0600611 // Now that we have the password clear the password field.
612 v.setText(null);
613
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700614 // Disable the password entry and back keypress while checking the password. These
615 // we either be re-enabled if the password was wrong or after the cooldown period.
Jason parks06c5ff42011-03-01 10:17:40 -0600616 mPasswordEntry.setEnabled(false);
Vikram Aggarwalde3c9cb2012-05-01 15:37:30 -0700617 mIgnoreBack = true;
Jason parks8fd5bc92011-01-12 16:03:31 -0600618
Ben Komalo04606752011-08-18 14:50:26 -0700619 Log.d(TAG, "Attempting to send command to decrypt");
Jason parks06c5ff42011-03-01 10:17:40 -0600620 new DecryptTask().execute(password);
Jason parks35933812011-01-21 15:48:20 -0600621
Jason parks8fd5bc92011-01-12 16:03:31 -0600622 return true;
623 }
624 return false;
625 }
David Brown8373b452011-06-20 12:38:45 -0700626
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700627 /**
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700628 * Set airplane mode on the device if it isn't an LTE device.
629 * Full story: In minimal boot mode, we cannot save any state. In particular, we cannot save
630 * any incoming SMS's. So SMSs that are received here will be silently dropped to the floor.
631 * That is bad. Also, we cannot receive any telephone calls in this state. So to avoid
632 * both these problems, we turn the radio off. However, on certain networks turning on and
633 * off the radio takes a long time. In such cases, we are better off leaving the radio
634 * running so the latency of an E911 call is short.
635 * The behavior after this is:
636 * 1. Emergency dialing: the emergency dialer has logic to force the device out of
637 * airplane mode and restart the radio.
638 * 2. Full boot: we read the persistent settings from the previous boot and restore the
639 * radio to whatever it was before it restarted. This also happens when rebooting a
640 * phone that has no encryption.
641 */
642 private final void setAirplaneModeIfNecessary() {
643 final boolean isLteDevice =
644 TelephonyManager.getDefault().getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
645 if (!isLteDevice) {
646 Log.d(TAG, "Going into airplane mode.");
Vikram Aggarwal6ebbd302012-05-04 14:09:52 -0700647 Settings.System.putInt(getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 1);
Vikram Aggarwalea1186d2012-05-03 15:35:03 -0700648 final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
649 intent.putExtra("state", true);
650 sendBroadcast(intent);
651 }
652 }
653
654 /**
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700655 * Code to update the state of, and handle clicks from, the "Emergency call" button.
656 *
657 * This code is mostly duplicated from the corresponding code in
658 * LockPatternUtils and LockPatternKeyguardView under frameworks/base.
659 */
David Brown8373b452011-06-20 12:38:45 -0700660 private void updateEmergencyCallButtonState() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700661 final Button emergencyCall = (Button) findViewById(R.id.emergencyCallButton);
David Brown8373b452011-06-20 12:38:45 -0700662 // The button isn't present at all in some configurations.
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700663 if (emergencyCall == null)
664 return;
David Brown8373b452011-06-20 12:38:45 -0700665
666 if (isEmergencyCallCapable()) {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700667 emergencyCall.setVisibility(View.VISIBLE);
668 emergencyCall.setOnClickListener(new View.OnClickListener() {
669 @Override
670
David Brown8373b452011-06-20 12:38:45 -0700671 public void onClick(View v) {
672 takeEmergencyCallAction();
673 }
674 });
675 } else {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700676 emergencyCall.setVisibility(View.GONE);
David Brown8373b452011-06-20 12:38:45 -0700677 return;
678 }
679
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700680 final int newState = TelephonyManager.getDefault().getCallState();
David Brown8373b452011-06-20 12:38:45 -0700681 int textId;
682 if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700683 // Show "return to call" text and show phone icon
David Brown8373b452011-06-20 12:38:45 -0700684 textId = R.string.cryptkeeper_return_to_call;
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700685 final int phoneCallIcon = R.drawable.stat_sys_phone_call;
686 emergencyCall.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
David Brown8373b452011-06-20 12:38:45 -0700687 } else {
688 textId = R.string.cryptkeeper_emergency_call;
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700689 final int emergencyIcon = R.drawable.ic_emergency;
690 emergencyCall.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
David Brown8373b452011-06-20 12:38:45 -0700691 }
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700692 emergencyCall.setText(textId);
David Brown8373b452011-06-20 12:38:45 -0700693 }
694
695 private boolean isEmergencyCallCapable() {
696 return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
697 }
698
699 private void takeEmergencyCallAction() {
700 if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
701 resumeCall();
702 } else {
703 launchEmergencyDialer();
704 }
705 }
706
707 private void resumeCall() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700708 final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
David Brown8373b452011-06-20 12:38:45 -0700709 if (phone != null) {
710 try {
711 phone.showCallScreen();
712 } catch (RemoteException e) {
713 Log.e(TAG, "Error calling ITelephony service: " + e);
714 }
715 }
716 }
717
718 private void launchEmergencyDialer() {
Vikram Aggarwalbfa3a642012-03-29 14:07:22 -0700719 final Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
David Brown8373b452011-06-20 12:38:45 -0700720 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
721 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
722 startActivity(intent);
723 }
Vikram Aggarwald1147252012-05-08 13:52:30 -0700724
725 /**
726 * Listen to key events so we can disable sounds when we get a keyinput in EditText.
727 */
728 private void delayAudioNotification() {
729 Log.d(TAG, "User entering password: delay audio notification");
730 mNotificationCountdown = 20;
731 }
732
733 @Override
734 public boolean onKey(View v, int keyCode, KeyEvent event) {
735 delayAudioNotification();
736 return false;
737 }
738
739 @Override
740 public boolean onTouch(View v, MotionEvent event) {
741 delayAudioNotification();
742 return false;
743 }
744
745 @Override
746 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
747 return;
748 }
749
750 @Override
751 public void onTextChanged(CharSequence s, int start, int before, int count) {
752 delayAudioNotification();
753 }
754
755 @Override
756 public void afterTextChanged(Editable s) {
757 return;
758 }
David Brown8373b452011-06-20 12:38:45 -0700759}