blob: 20bf7cee2e5b8f5a6b4435046baa2dfab93d1624 [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
19import com.android.internal.widget.PasswordEntryKeyboardHelper;
20import com.android.internal.widget.PasswordEntryKeyboardView;
21
22import android.app.Activity;
23import android.app.StatusBarManager;
24import android.content.ComponentName;
25import android.content.Context;
Jason parksec5a45e2011-01-18 15:28:36 -060026import android.content.Intent;
Jason parks8fd5bc92011-01-12 16:03:31 -060027import android.content.pm.PackageManager;
Jason parks75c085e2011-02-10 14:03:47 -060028import android.graphics.Rect;
Jason parks8fd5bc92011-01-12 16:03:31 -060029import android.inputmethodservice.KeyboardView;
Jason parks06c5ff42011-03-01 10:17:40 -060030import android.os.AsyncTask;
Jason parks8fd5bc92011-01-12 16:03:31 -060031import android.os.Bundle;
Jason parksec5a45e2011-01-18 15:28:36 -060032import android.os.Handler;
Jason parks8fd5bc92011-01-12 16:03:31 -060033import android.os.IBinder;
Jason parksec5a45e2011-01-18 15:28:36 -060034import android.os.Message;
Jason parks35933812011-01-21 15:48:20 -060035import android.os.PowerManager;
Jason parks8fd5bc92011-01-12 16:03:31 -060036import android.os.ServiceManager;
37import android.os.SystemProperties;
38import android.os.storage.IMountService;
Jason parksec5a45e2011-01-18 15:28:36 -060039import android.text.TextUtils;
Jason parks75c085e2011-02-10 14:03:47 -060040import android.util.AttributeSet;
Jason parks8fd5bc92011-01-12 16:03:31 -060041import android.util.Log;
42import android.view.KeyEvent;
Jason parks75c085e2011-02-10 14:03:47 -060043import android.view.MotionEvent;
Andy Stadler13d62042011-01-31 19:21:37 -080044import android.view.View;
45import android.view.View.OnClickListener;
Jason parks8fd5bc92011-01-12 16:03:31 -060046import android.view.inputmethod.EditorInfo;
Jason parks75c085e2011-02-10 14:03:47 -060047import android.view.inputmethod.InputMethodManager;
Andy Stadler13d62042011-01-31 19:21:37 -080048import android.widget.Button;
Jason parksec5a45e2011-01-18 15:28:36 -060049import android.widget.EditText;
50import android.widget.ProgressBar;
Jason parks8fd5bc92011-01-12 16:03:31 -060051import android.widget.TextView;
52
Jason parks8fd5bc92011-01-12 16:03:31 -060053public class CryptKeeper extends Activity implements TextView.OnEditorActionListener {
Jason parksec5a45e2011-01-18 15:28:36 -060054 private static final String TAG = "CryptKeeper";
Jason parks35933812011-01-21 15:48:20 -060055
Jason parks8fd5bc92011-01-12 16:03:31 -060056 private static final String DECRYPT_STATE = "trigger_restart_framework";
Jason parksec5a45e2011-01-18 15:28:36 -060057
58 private static final int UPDATE_PROGRESS = 1;
59 private static final int COOLDOWN = 2;
60
61 private static final int MAX_FAILED_ATTEMPTS = 30;
62 private static final int COOL_DOWN_ATTEMPTS = 10;
63 private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
64
Andy Stadler14997402011-02-01 15:34:59 -080065 private int mCooldown;
66 PowerManager.WakeLock mWakeLock;
Jason parks06c5ff42011-03-01 10:17:40 -060067 private EditText mPasswordEntry;
Andy Stadler14997402011-02-01 15:34:59 -080068
69 /**
70 * Used to propagate state through configuration changes (e.g. screen rotation)
71 */
72 private static class NonConfigurationInstanceState {
73 final PowerManager.WakeLock wakelock;
74
75 NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
76 wakelock = _wakelock;
77 }
78 }
79
Jason parksf1dbf552011-01-24 16:19:28 -060080 // This activity is used to fade the screen to black after the password is entered.
81 public static class Blank extends Activity {
Andy Stadler13d62042011-01-31 19:21:37 -080082 @Override
83 public void onCreate(Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 setContentView(R.layout.crypt_keeper_blank);
86 }
Jason parksf1dbf552011-01-24 16:19:28 -060087 }
Jason parksec5a45e2011-01-18 15:28:36 -060088
Jason parks75c085e2011-02-10 14:03:47 -060089 // Use a custom EditText to prevent the input method from showing.
90 public static class CryptEditText extends EditText {
91 InputMethodManager imm;
92
93 public CryptEditText(Context context, AttributeSet attrs) {
94 super(context, attrs);
95 imm = ((InputMethodManager) getContext().
96 getSystemService(Context.INPUT_METHOD_SERVICE));
97 }
98
99 @Override
100 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
101 super.onFocusChanged(focused, direction, previouslyFocusedRect);
102
103 if (focused && imm != null && imm.isActive(this)) {
104 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
105 }
106 }
107
108 @Override
109 public boolean onTouchEvent(MotionEvent event) {
110 boolean handled = super.onTouchEvent(event);
111
112 if (imm != null && imm.isActive(this)) {
113 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
114 }
115
116 return handled;
117 }
118 }
119
Jason parks06c5ff42011-03-01 10:17:40 -0600120 private class DecryptTask extends AsyncTask<String, Void, Integer> {
121 @Override
122 protected Integer doInBackground(String... params) {
123 IMountService service = getMountService();
124 try {
125 return service.decryptStorage(params[0]);
126 } catch (Exception e) {
127 Log.e(TAG, "Error while decrypting...", e);
128 return -1;
129 }
130 }
131
132 @Override
133 protected void onPostExecute(Integer failedAttempts) {
134 if (failedAttempts == 0) {
135 // The password was entered successfully. Start the Blank activity
136 // so this activity animates to black before the devices starts. Note
137 // It has 1 second to complete the animation or it will be frozen
138 // until the boot animation comes back up.
139 Intent intent = new Intent(CryptKeeper.this, Blank.class);
140 finish();
141 startActivity(intent);
142 } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
143 // Factory reset the device.
144 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
145 } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
146 mCooldown = COOL_DOWN_INTERVAL;
147 cooldown();
148 } else {
149 TextView tv = (TextView) findViewById(R.id.status);
150 tv.setText(R.string.try_again);
151 tv.setVisibility(View.VISIBLE);
152
153 // Reenable the password entry
154 mPasswordEntry.setEnabled(true);
155 }
156 }
157 }
Jason parks75c085e2011-02-10 14:03:47 -0600158
Jason parksec5a45e2011-01-18 15:28:36 -0600159 private Handler mHandler = new Handler() {
160 @Override
161 public void handleMessage(Message msg) {
Jason parksec5a45e2011-01-18 15:28:36 -0600162 switch (msg.what) {
Jason parksec5a45e2011-01-18 15:28:36 -0600163 case UPDATE_PROGRESS:
Jason parksf8217302011-01-26 13:11:42 -0600164 updateProgress();
Jason parksec5a45e2011-01-18 15:28:36 -0600165 break;
Jason parks35933812011-01-21 15:48:20 -0600166
Jason parksec5a45e2011-01-18 15:28:36 -0600167 case COOLDOWN:
Jason parksf8217302011-01-26 13:11:42 -0600168 cooldown();
Jason parksec5a45e2011-01-18 15:28:36 -0600169 break;
170 }
171 }
172 };
Jason parks35933812011-01-21 15:48:20 -0600173
Jason parks8fd5bc92011-01-12 16:03:31 -0600174 @Override
175 public void onCreate(Bundle savedInstanceState) {
176 super.onCreate(savedInstanceState);
Jason parks35933812011-01-21 15:48:20 -0600177
Andy Stadler95974062011-02-01 17:35:20 -0800178 // If we are not encrypted or encrypting, get out quickly.
Jason parks8fd5bc92011-01-12 16:03:31 -0600179 String state = SystemProperties.get("vold.decrypt");
180 if ("".equals(state) || DECRYPT_STATE.equals(state)) {
Jason parks35933812011-01-21 15:48:20 -0600181 // Disable the crypt keeper.
Jason parks8fd5bc92011-01-12 16:03:31 -0600182 PackageManager pm = getPackageManager();
183 ComponentName name = new ComponentName(this, CryptKeeper.class);
184 pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
185 return;
186 }
Jason parks35933812011-01-21 15:48:20 -0600187
Jason parks39f1e042011-01-20 23:29:28 -0600188 // Disable the status bar
189 StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
Andy Stadler13d62042011-01-31 19:21:37 -0800190 sbm.disable(StatusBarManager.DISABLE_EXPAND
191 | StatusBarManager.DISABLE_NOTIFICATION_ICONS
Jason parks39f1e042011-01-20 23:29:28 -0600192 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
Andy Stadler13d62042011-01-31 19:21:37 -0800193 | StatusBarManager.DISABLE_SYSTEM_INFO
194 | StatusBarManager.DISABLE_NAVIGATION
195 | StatusBarManager.DISABLE_BACK);
Andy Stadler14997402011-02-01 15:34:59 -0800196
197 // Check for (and recover) retained instance data
198 Object lastInstance = getLastNonConfigurationInstance();
199 if (lastInstance instanceof NonConfigurationInstanceState) {
200 NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
201 mWakeLock = retained.wakelock;
202 }
Jason parksec5a45e2011-01-18 15:28:36 -0600203 }
Jason parks35933812011-01-21 15:48:20 -0600204
Andy Stadler95974062011-02-01 17:35:20 -0800205 /**
206 * Note, we defer the state check and screen setup to onStart() because this will be
207 * re-run if the user clicks the power button (sleeping/waking the screen), and this is
208 * especially important if we were to lose the wakelock for any reason.
209 */
210 @Override
211 public void onStart() {
212 super.onStart();
213
214 // Check to see why we were started.
215 String progress = SystemProperties.get("vold.encrypt_progress");
216 if (!"".equals(progress)) {
217 setContentView(R.layout.crypt_keeper_progress);
218 encryptionProgressInit();
219 } else {
220 setContentView(R.layout.crypt_keeper_password_entry);
221 passwordEntryInit();
222 }
223 }
224
Jason parksf8217302011-01-26 13:11:42 -0600225 @Override
226 public void onStop() {
227 super.onStop();
228
229 mHandler.removeMessages(COOLDOWN);
230 mHandler.removeMessages(UPDATE_PROGRESS);
Andy Stadler14997402011-02-01 15:34:59 -0800231 }
232
233 /**
234 * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
235 * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
236 * mWakeLock so the subsequent call to onDestroy does not release it.
237 */
238 @Override
239 public Object onRetainNonConfigurationInstance() {
240 NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
241 mWakeLock = null;
242 return state;
243 }
244
245 @Override
246 public void onDestroy() {
247 super.onDestroy();
Jason parksf8217302011-01-26 13:11:42 -0600248
249 if (mWakeLock != null) {
250 mWakeLock.release();
251 mWakeLock = null;
252 }
253 }
254
Jason parksec5a45e2011-01-18 15:28:36 -0600255 private void encryptionProgressInit() {
Jason parks35933812011-01-21 15:48:20 -0600256 // Accquire a partial wakelock to prevent the device from sleeping. Note
257 // we never release this wakelock as we will be restarted after the device
258 // is encrypted.
259
260 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
Jason parksf8217302011-01-26 13:11:42 -0600261 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
Jason parks35933812011-01-21 15:48:20 -0600262
Jason parksf8217302011-01-26 13:11:42 -0600263 mWakeLock.acquire();
Jason parks35933812011-01-21 15:48:20 -0600264
Jason parksf8217302011-01-26 13:11:42 -0600265 ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
266 progressBar.setIndeterminate(true);
267
268 updateProgress();
269 }
270
Andy Stadler13d62042011-01-31 19:21:37 -0800271 private void showFactoryReset() {
272 // Hide the encryption-bot to make room for the "factory reset" button
273 findViewById(R.id.encroid).setVisibility(View.GONE);
274
275 // Show the reset button, failure text, and a divider
276 Button button = (Button) findViewById(R.id.factory_reset);
277 button.setVisibility(View.VISIBLE);
278 button.setOnClickListener(new OnClickListener() {
279 public void onClick(View v) {
280 // Factory reset the device.
281 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
282 }
283 });
284
285 TextView tv = (TextView) findViewById(R.id.title);
286 tv.setText(R.string.crypt_keeper_failed_title);
287
288 tv = (TextView) findViewById(R.id.status);
289 tv.setText(R.string.crypt_keeper_failed_summary);
290
291 View view = findViewById(R.id.bottom_divider);
292 view.setVisibility(View.VISIBLE);
293 }
294
Jason parksf8217302011-01-26 13:11:42 -0600295 private void updateProgress() {
296 String state = SystemProperties.get("vold.encrypt_progress");
297
Andy Stadler13d62042011-01-31 19:21:37 -0800298 if ("error_partially_encrypted".equals(state)) {
299 showFactoryReset();
300 return;
301 }
302
Jason parksf8217302011-01-26 13:11:42 -0600303 int progress = 0;
304 try {
305 progress = Integer.parseInt(state);
306 } catch (Exception e) {
307 Log.w(TAG, "Error parsing progress: " + e.toString());
308 }
309
310 CharSequence status = getText(R.string.crypt_keeper_setup_description);
311 TextView tv = (TextView) findViewById(R.id.status);
312 tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
313
314 // Check the progress every 5 seconds
315 mHandler.removeMessages(UPDATE_PROGRESS);
316 mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000);
317 }
318
319 private void cooldown() {
320 TextView tv = (TextView) findViewById(R.id.status);
Andy Stadler13d62042011-01-31 19:21:37 -0800321
Jason parksf8217302011-01-26 13:11:42 -0600322 if (mCooldown <= 0) {
323 // Re-enable the password entry
Jason parks06c5ff42011-03-01 10:17:40 -0600324 mPasswordEntry.setEnabled(true);
Jason parksf8217302011-01-26 13:11:42 -0600325
Andy Stadler13d62042011-01-31 19:21:37 -0800326 tv.setVisibility(View.GONE);
Jason parksf8217302011-01-26 13:11:42 -0600327 } else {
Andy Stadler13d62042011-01-31 19:21:37 -0800328 CharSequence template = getText(R.string.crypt_keeper_cooldown);
329 tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
330
331 tv.setVisibility(View.VISIBLE);
Jason parksf8217302011-01-26 13:11:42 -0600332
333 mCooldown--;
334 mHandler.removeMessages(COOLDOWN);
335 mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
336 }
Jason parksec5a45e2011-01-18 15:28:36 -0600337 }
Jason parks35933812011-01-21 15:48:20 -0600338
Jason parksec5a45e2011-01-18 15:28:36 -0600339 private void passwordEntryInit() {
Jason parks06c5ff42011-03-01 10:17:40 -0600340 mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
341 mPasswordEntry.setOnEditorActionListener(this);
Jason parks35933812011-01-21 15:48:20 -0600342
Jason parks8fd5bc92011-01-12 16:03:31 -0600343 KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
Jason parks35933812011-01-21 15:48:20 -0600344
Jason parks00046d62011-06-13 17:38:45 -0500345 if (keyboardView != null) {
346 PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
347 keyboardView, mPasswordEntry, false);
348 keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
349 }
Jason parks8fd5bc92011-01-12 16:03:31 -0600350 }
351
352 private IMountService getMountService() {
353 IBinder service = ServiceManager.getService("mount");
354 if (service != null) {
355 return IMountService.Stub.asInterface(service);
356 }
357 return null;
358 }
359
360 @Override
361 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Jason parks00046d62011-06-13 17:38:45 -0500362 if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
Jason parks8fd5bc92011-01-12 16:03:31 -0600363 // Get the password
364 String password = v.getText().toString();
365
Jason parksec5a45e2011-01-18 15:28:36 -0600366 if (TextUtils.isEmpty(password)) {
367 return true;
368 }
Jason parks35933812011-01-21 15:48:20 -0600369
Jason parks8fd5bc92011-01-12 16:03:31 -0600370 // Now that we have the password clear the password field.
371 v.setText(null);
372
Jason parks06c5ff42011-03-01 10:17:40 -0600373 // Disable the password entry while checking the password. This
374 // we either be reenabled if the password was wrong or after the
375 // cooldown period.
376 mPasswordEntry.setEnabled(false);
Jason parks8fd5bc92011-01-12 16:03:31 -0600377
Jason parks06c5ff42011-03-01 10:17:40 -0600378 new DecryptTask().execute(password);
Jason parks35933812011-01-21 15:48:20 -0600379
Jason parks8fd5bc92011-01-12 16:03:31 -0600380 return true;
381 }
382 return false;
383 }
384}