blob: 612f4c05bce6e1eaa8e3835be1ab5af91b7e4948 [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
David Brown8373b452011-06-20 12:38:45 -070019import com.android.internal.telephony.ITelephony;
Jason parks8fd5bc92011-01-12 16:03:31 -060020import com.android.internal.widget.PasswordEntryKeyboardHelper;
21import com.android.internal.widget.PasswordEntryKeyboardView;
22
23import android.app.Activity;
24import android.app.StatusBarManager;
25import android.content.ComponentName;
26import android.content.Context;
Jason parksec5a45e2011-01-18 15:28:36 -060027import android.content.Intent;
Jason parks8fd5bc92011-01-12 16:03:31 -060028import android.content.pm.PackageManager;
Jason parks75c085e2011-02-10 14:03:47 -060029import android.graphics.Rect;
Jason parks8fd5bc92011-01-12 16:03:31 -060030import android.inputmethodservice.KeyboardView;
Jason parks06c5ff42011-03-01 10:17:40 -060031import android.os.AsyncTask;
Jason parks8fd5bc92011-01-12 16:03:31 -060032import android.os.Bundle;
Jason parksec5a45e2011-01-18 15:28:36 -060033import android.os.Handler;
Jason parks8fd5bc92011-01-12 16:03:31 -060034import android.os.IBinder;
Jason parksec5a45e2011-01-18 15:28:36 -060035import android.os.Message;
Jason parks35933812011-01-21 15:48:20 -060036import android.os.PowerManager;
David Brown8373b452011-06-20 12:38:45 -070037import android.os.RemoteException;
Jason parks8fd5bc92011-01-12 16:03:31 -060038import android.os.ServiceManager;
39import android.os.SystemProperties;
40import android.os.storage.IMountService;
David Brown8373b452011-06-20 12:38:45 -070041import android.telephony.TelephonyManager;
Jason parksec5a45e2011-01-18 15:28:36 -060042import android.text.TextUtils;
Jason parks75c085e2011-02-10 14:03:47 -060043import android.util.AttributeSet;
Jason parks8fd5bc92011-01-12 16:03:31 -060044import android.util.Log;
45import android.view.KeyEvent;
Jason parks75c085e2011-02-10 14:03:47 -060046import android.view.MotionEvent;
Andy Stadler13d62042011-01-31 19:21:37 -080047import android.view.View;
48import android.view.View.OnClickListener;
Jason parks8fd5bc92011-01-12 16:03:31 -060049import android.view.inputmethod.EditorInfo;
Jason parks75c085e2011-02-10 14:03:47 -060050import android.view.inputmethod.InputMethodManager;
Andy Stadler13d62042011-01-31 19:21:37 -080051import android.widget.Button;
Jason parksec5a45e2011-01-18 15:28:36 -060052import android.widget.EditText;
53import android.widget.ProgressBar;
Jason parks8fd5bc92011-01-12 16:03:31 -060054import android.widget.TextView;
55
Jason parks8fd5bc92011-01-12 16:03:31 -060056public class CryptKeeper extends Activity implements TextView.OnEditorActionListener {
Jason parksec5a45e2011-01-18 15:28:36 -060057 private static final String TAG = "CryptKeeper";
Jason parks35933812011-01-21 15:48:20 -060058
Jason parks8fd5bc92011-01-12 16:03:31 -060059 private static final String DECRYPT_STATE = "trigger_restart_framework";
Jason parksec5a45e2011-01-18 15:28:36 -060060
61 private static final int UPDATE_PROGRESS = 1;
62 private static final int COOLDOWN = 2;
63
64 private static final int MAX_FAILED_ATTEMPTS = 30;
65 private static final int COOL_DOWN_ATTEMPTS = 10;
66 private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
67
David Brown8373b452011-06-20 12:38:45 -070068 // Intent action for launching the Emergency Dialer activity.
69 static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
70
Andy Stadler14997402011-02-01 15:34:59 -080071 private int mCooldown;
72 PowerManager.WakeLock mWakeLock;
Jason parks06c5ff42011-03-01 10:17:40 -060073 private EditText mPasswordEntry;
Andy Stadler14997402011-02-01 15:34:59 -080074
75 /**
76 * Used to propagate state through configuration changes (e.g. screen rotation)
77 */
78 private static class NonConfigurationInstanceState {
79 final PowerManager.WakeLock wakelock;
80
81 NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
82 wakelock = _wakelock;
83 }
84 }
85
Jason parksf1dbf552011-01-24 16:19:28 -060086 // This activity is used to fade the screen to black after the password is entered.
87 public static class Blank extends Activity {
Andy Stadler13d62042011-01-31 19:21:37 -080088 @Override
89 public void onCreate(Bundle savedInstanceState) {
90 super.onCreate(savedInstanceState);
91 setContentView(R.layout.crypt_keeper_blank);
92 }
Jason parksf1dbf552011-01-24 16:19:28 -060093 }
Jason parksec5a45e2011-01-18 15:28:36 -060094
Jason parks75c085e2011-02-10 14:03:47 -060095 // Use a custom EditText to prevent the input method from showing.
96 public static class CryptEditText extends EditText {
97 InputMethodManager imm;
98
99 public CryptEditText(Context context, AttributeSet attrs) {
100 super(context, attrs);
101 imm = ((InputMethodManager) getContext().
102 getSystemService(Context.INPUT_METHOD_SERVICE));
103 }
104
105 @Override
106 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
107 super.onFocusChanged(focused, direction, previouslyFocusedRect);
108
109 if (focused && imm != null && imm.isActive(this)) {
110 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
111 }
112 }
113
114 @Override
115 public boolean onTouchEvent(MotionEvent event) {
116 boolean handled = super.onTouchEvent(event);
117
118 if (imm != null && imm.isActive(this)) {
119 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
120 }
121
122 return handled;
123 }
124 }
125
Jason parks06c5ff42011-03-01 10:17:40 -0600126 private class DecryptTask extends AsyncTask<String, Void, Integer> {
127 @Override
128 protected Integer doInBackground(String... params) {
129 IMountService service = getMountService();
130 try {
131 return service.decryptStorage(params[0]);
132 } catch (Exception e) {
133 Log.e(TAG, "Error while decrypting...", e);
134 return -1;
135 }
136 }
137
138 @Override
139 protected void onPostExecute(Integer failedAttempts) {
140 if (failedAttempts == 0) {
141 // The password was entered successfully. Start the Blank activity
142 // so this activity animates to black before the devices starts. Note
143 // It has 1 second to complete the animation or it will be frozen
144 // until the boot animation comes back up.
145 Intent intent = new Intent(CryptKeeper.this, Blank.class);
146 finish();
147 startActivity(intent);
148 } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
149 // Factory reset the device.
150 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
151 } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
152 mCooldown = COOL_DOWN_INTERVAL;
153 cooldown();
154 } else {
155 TextView tv = (TextView) findViewById(R.id.status);
156 tv.setText(R.string.try_again);
157 tv.setVisibility(View.VISIBLE);
158
159 // Reenable the password entry
160 mPasswordEntry.setEnabled(true);
161 }
162 }
163 }
Jason parks75c085e2011-02-10 14:03:47 -0600164
Jason parksec5a45e2011-01-18 15:28:36 -0600165 private Handler mHandler = new Handler() {
166 @Override
167 public void handleMessage(Message msg) {
Jason parksec5a45e2011-01-18 15:28:36 -0600168 switch (msg.what) {
Jason parksec5a45e2011-01-18 15:28:36 -0600169 case UPDATE_PROGRESS:
Jason parksf8217302011-01-26 13:11:42 -0600170 updateProgress();
Jason parksec5a45e2011-01-18 15:28:36 -0600171 break;
Jason parks35933812011-01-21 15:48:20 -0600172
Jason parksec5a45e2011-01-18 15:28:36 -0600173 case COOLDOWN:
Jason parksf8217302011-01-26 13:11:42 -0600174 cooldown();
Jason parksec5a45e2011-01-18 15:28:36 -0600175 break;
176 }
177 }
178 };
Jason parks35933812011-01-21 15:48:20 -0600179
Jason parks8fd5bc92011-01-12 16:03:31 -0600180 @Override
181 public void onCreate(Bundle savedInstanceState) {
182 super.onCreate(savedInstanceState);
Jason parks35933812011-01-21 15:48:20 -0600183
Andy Stadler95974062011-02-01 17:35:20 -0800184 // If we are not encrypted or encrypting, get out quickly.
Jason parks8fd5bc92011-01-12 16:03:31 -0600185 String state = SystemProperties.get("vold.decrypt");
186 if ("".equals(state) || DECRYPT_STATE.equals(state)) {
Jason parks35933812011-01-21 15:48:20 -0600187 // Disable the crypt keeper.
Jason parks8fd5bc92011-01-12 16:03:31 -0600188 PackageManager pm = getPackageManager();
189 ComponentName name = new ComponentName(this, CryptKeeper.class);
190 pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
191 return;
192 }
Jason parks35933812011-01-21 15:48:20 -0600193
Jason parks39f1e042011-01-20 23:29:28 -0600194 // Disable the status bar
195 StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
Andy Stadler13d62042011-01-31 19:21:37 -0800196 sbm.disable(StatusBarManager.DISABLE_EXPAND
197 | StatusBarManager.DISABLE_NOTIFICATION_ICONS
Jason parks39f1e042011-01-20 23:29:28 -0600198 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
Andy Stadler13d62042011-01-31 19:21:37 -0800199 | StatusBarManager.DISABLE_SYSTEM_INFO
200 | StatusBarManager.DISABLE_NAVIGATION
201 | StatusBarManager.DISABLE_BACK);
Andy Stadler14997402011-02-01 15:34:59 -0800202
203 // Check for (and recover) retained instance data
204 Object lastInstance = getLastNonConfigurationInstance();
205 if (lastInstance instanceof NonConfigurationInstanceState) {
206 NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
207 mWakeLock = retained.wakelock;
208 }
Jason parksec5a45e2011-01-18 15:28:36 -0600209 }
Jason parks35933812011-01-21 15:48:20 -0600210
Andy Stadler95974062011-02-01 17:35:20 -0800211 /**
212 * Note, we defer the state check and screen setup to onStart() because this will be
213 * re-run if the user clicks the power button (sleeping/waking the screen), and this is
214 * especially important if we were to lose the wakelock for any reason.
215 */
216 @Override
217 public void onStart() {
218 super.onStart();
219
220 // Check to see why we were started.
221 String progress = SystemProperties.get("vold.encrypt_progress");
222 if (!"".equals(progress)) {
223 setContentView(R.layout.crypt_keeper_progress);
224 encryptionProgressInit();
225 } else {
226 setContentView(R.layout.crypt_keeper_password_entry);
227 passwordEntryInit();
228 }
229 }
230
Jason parksf8217302011-01-26 13:11:42 -0600231 @Override
232 public void onStop() {
233 super.onStop();
234
235 mHandler.removeMessages(COOLDOWN);
236 mHandler.removeMessages(UPDATE_PROGRESS);
Andy Stadler14997402011-02-01 15:34:59 -0800237 }
238
239 /**
240 * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
241 * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
242 * mWakeLock so the subsequent call to onDestroy does not release it.
243 */
244 @Override
245 public Object onRetainNonConfigurationInstance() {
246 NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
247 mWakeLock = null;
248 return state;
249 }
250
251 @Override
252 public void onDestroy() {
253 super.onDestroy();
Jason parksf8217302011-01-26 13:11:42 -0600254
255 if (mWakeLock != null) {
256 mWakeLock.release();
257 mWakeLock = null;
258 }
259 }
260
Jason parksec5a45e2011-01-18 15:28:36 -0600261 private void encryptionProgressInit() {
Jason parks35933812011-01-21 15:48:20 -0600262 // Accquire a partial wakelock to prevent the device from sleeping. Note
263 // we never release this wakelock as we will be restarted after the device
264 // is encrypted.
265
266 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
Jason parksf8217302011-01-26 13:11:42 -0600267 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
Jason parks35933812011-01-21 15:48:20 -0600268
Jason parksf8217302011-01-26 13:11:42 -0600269 mWakeLock.acquire();
Jason parks35933812011-01-21 15:48:20 -0600270
Jason parksf8217302011-01-26 13:11:42 -0600271 ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
272 progressBar.setIndeterminate(true);
273
274 updateProgress();
275 }
276
Andy Stadler13d62042011-01-31 19:21:37 -0800277 private void showFactoryReset() {
278 // Hide the encryption-bot to make room for the "factory reset" button
279 findViewById(R.id.encroid).setVisibility(View.GONE);
280
281 // Show the reset button, failure text, and a divider
282 Button button = (Button) findViewById(R.id.factory_reset);
283 button.setVisibility(View.VISIBLE);
284 button.setOnClickListener(new OnClickListener() {
285 public void onClick(View v) {
286 // Factory reset the device.
287 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
288 }
289 });
290
291 TextView tv = (TextView) findViewById(R.id.title);
292 tv.setText(R.string.crypt_keeper_failed_title);
293
294 tv = (TextView) findViewById(R.id.status);
295 tv.setText(R.string.crypt_keeper_failed_summary);
296
297 View view = findViewById(R.id.bottom_divider);
298 view.setVisibility(View.VISIBLE);
299 }
300
Jason parksf8217302011-01-26 13:11:42 -0600301 private void updateProgress() {
302 String state = SystemProperties.get("vold.encrypt_progress");
303
Andy Stadler13d62042011-01-31 19:21:37 -0800304 if ("error_partially_encrypted".equals(state)) {
305 showFactoryReset();
306 return;
307 }
308
Jason parksf8217302011-01-26 13:11:42 -0600309 int progress = 0;
310 try {
311 progress = Integer.parseInt(state);
312 } catch (Exception e) {
313 Log.w(TAG, "Error parsing progress: " + e.toString());
314 }
315
316 CharSequence status = getText(R.string.crypt_keeper_setup_description);
317 TextView tv = (TextView) findViewById(R.id.status);
318 tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
319
320 // Check the progress every 5 seconds
321 mHandler.removeMessages(UPDATE_PROGRESS);
322 mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000);
323 }
324
325 private void cooldown() {
326 TextView tv = (TextView) findViewById(R.id.status);
Andy Stadler13d62042011-01-31 19:21:37 -0800327
Jason parksf8217302011-01-26 13:11:42 -0600328 if (mCooldown <= 0) {
329 // Re-enable the password entry
Jason parks06c5ff42011-03-01 10:17:40 -0600330 mPasswordEntry.setEnabled(true);
Jason parksf8217302011-01-26 13:11:42 -0600331
Andy Stadler13d62042011-01-31 19:21:37 -0800332 tv.setVisibility(View.GONE);
Jason parksf8217302011-01-26 13:11:42 -0600333 } else {
Andy Stadler13d62042011-01-31 19:21:37 -0800334 CharSequence template = getText(R.string.crypt_keeper_cooldown);
335 tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
336
337 tv.setVisibility(View.VISIBLE);
Jason parksf8217302011-01-26 13:11:42 -0600338
339 mCooldown--;
340 mHandler.removeMessages(COOLDOWN);
341 mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
342 }
Jason parksec5a45e2011-01-18 15:28:36 -0600343 }
Jason parks35933812011-01-21 15:48:20 -0600344
Jason parksec5a45e2011-01-18 15:28:36 -0600345 private void passwordEntryInit() {
Jason parks06c5ff42011-03-01 10:17:40 -0600346 mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
347 mPasswordEntry.setOnEditorActionListener(this);
Jason parks35933812011-01-21 15:48:20 -0600348
Jason parks8fd5bc92011-01-12 16:03:31 -0600349 KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
Jason parks35933812011-01-21 15:48:20 -0600350
Jason parks00046d62011-06-13 17:38:45 -0500351 if (keyboardView != null) {
352 PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
353 keyboardView, mPasswordEntry, false);
354 keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
355 }
David Brown8373b452011-06-20 12:38:45 -0700356
357 updateEmergencyCallButtonState();
Jason parks8fd5bc92011-01-12 16:03:31 -0600358 }
359
360 private IMountService getMountService() {
361 IBinder service = ServiceManager.getService("mount");
362 if (service != null) {
363 return IMountService.Stub.asInterface(service);
364 }
365 return null;
366 }
367
368 @Override
369 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Jason parks00046d62011-06-13 17:38:45 -0500370 if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
Jason parks8fd5bc92011-01-12 16:03:31 -0600371 // Get the password
372 String password = v.getText().toString();
373
Jason parksec5a45e2011-01-18 15:28:36 -0600374 if (TextUtils.isEmpty(password)) {
375 return true;
376 }
Jason parks35933812011-01-21 15:48:20 -0600377
Jason parks8fd5bc92011-01-12 16:03:31 -0600378 // Now that we have the password clear the password field.
379 v.setText(null);
380
Jason parks06c5ff42011-03-01 10:17:40 -0600381 // Disable the password entry while checking the password. This
382 // we either be reenabled if the password was wrong or after the
383 // cooldown period.
384 mPasswordEntry.setEnabled(false);
Jason parks8fd5bc92011-01-12 16:03:31 -0600385
Jason parks06c5ff42011-03-01 10:17:40 -0600386 new DecryptTask().execute(password);
Jason parks35933812011-01-21 15:48:20 -0600387
Jason parks8fd5bc92011-01-12 16:03:31 -0600388 return true;
389 }
390 return false;
391 }
David Brown8373b452011-06-20 12:38:45 -0700392
393 //
394 // Code to update the state of, and handle clicks from, the "Emergency call" button.
395 //
396 // This code is mostly duplicated from the corresponding code in
397 // LockPatternUtils and LockPatternKeyguardView under frameworks/base.
398 //
399
400 private void updateEmergencyCallButtonState() {
401 Button button = (Button) findViewById(R.id.emergencyCallButton);
402 // The button isn't present at all in some configurations.
403 if (button == null) return;
404
405 if (isEmergencyCallCapable()) {
406 button.setVisibility(View.VISIBLE);
407 button.setOnClickListener(new View.OnClickListener() {
408 public void onClick(View v) {
409 takeEmergencyCallAction();
410 }
411 });
412 } else {
413 button.setVisibility(View.GONE);
414 return;
415 }
416
417 int newState = TelephonyManager.getDefault().getCallState();
418 int textId;
419 if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
420 // show "return to call" text and show phone icon
421 textId = R.string.cryptkeeper_return_to_call;
422 int phoneCallIcon = R.drawable.stat_sys_phone_call;
423 button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
424 } else {
425 textId = R.string.cryptkeeper_emergency_call;
426 int emergencyIcon = R.drawable.ic_emergency;
427 button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
428 }
429 button.setText(textId);
430 }
431
432 private boolean isEmergencyCallCapable() {
433 return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
434 }
435
436 private void takeEmergencyCallAction() {
437 if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
438 resumeCall();
439 } else {
440 launchEmergencyDialer();
441 }
442 }
443
444 private void resumeCall() {
445 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
446 if (phone != null) {
447 try {
448 phone.showCallScreen();
449 } catch (RemoteException e) {
450 Log.e(TAG, "Error calling ITelephony service: " + e);
451 }
452 }
453 }
454
455 private void launchEmergencyDialer() {
456 Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
457 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
458 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
459 startActivity(intent);
460 }
461}