CryptKeeper.java revision 75c085ee890744cdd4b90c72f8b50e6aeeb31e88
1f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood/* 2f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Copyright (C) 2011 The Android Open Source Project 3f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * 4f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Licensed under the Apache License, Version 2.0 (the "License"); 5f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * you may not use this file except in compliance with the License. 6f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * You may obtain a copy of the License at 7f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * 8f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * http://www.apache.org/licenses/LICENSE-2.0 9f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * 10f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Unless required by applicable law or agreed to in writing, software 11f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * 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; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.graphics.Rect; 29import android.inputmethodservice.KeyboardView; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.IBinder; 33import android.os.Message; 34import android.os.PowerManager; 35import android.os.ServiceManager; 36import android.os.SystemProperties; 37import android.os.storage.IMountService; 38import android.text.TextUtils; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.view.KeyEvent; 42import android.view.MotionEvent; 43import android.view.View; 44import android.view.View.OnClickListener; 45import android.view.inputmethod.EditorInfo; 46import android.view.inputmethod.InputMethodManager; 47import android.widget.Button; 48import android.widget.EditText; 49import android.widget.ProgressBar; 50import android.widget.TextView; 51 52public class CryptKeeper extends Activity implements TextView.OnEditorActionListener { 53 private static final String TAG = "CryptKeeper"; 54 55 private static final String DECRYPT_STATE = "trigger_restart_framework"; 56 57 private static final int UPDATE_PROGRESS = 1; 58 private static final int COOLDOWN = 2; 59 60 private static final int MAX_FAILED_ATTEMPTS = 30; 61 private static final int COOL_DOWN_ATTEMPTS = 10; 62 private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds 63 64 private int mCooldown; 65 PowerManager.WakeLock mWakeLock; 66 67 /** 68 * Used to propagate state through configuration changes (e.g. screen rotation) 69 */ 70 private static class NonConfigurationInstanceState { 71 final PowerManager.WakeLock wakelock; 72 73 NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) { 74 wakelock = _wakelock; 75 } 76 } 77 78 // This activity is used to fade the screen to black after the password is entered. 79 public static class Blank extends Activity { 80 @Override 81 public void onCreate(Bundle savedInstanceState) { 82 super.onCreate(savedInstanceState); 83 setContentView(R.layout.crypt_keeper_blank); 84 } 85 } 86 87 // Use a custom EditText to prevent the input method from showing. 88 public static class CryptEditText extends EditText { 89 InputMethodManager imm; 90 91 public CryptEditText(Context context, AttributeSet attrs) { 92 super(context, attrs); 93 imm = ((InputMethodManager) getContext(). 94 getSystemService(Context.INPUT_METHOD_SERVICE)); 95 } 96 97 @Override 98 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 99 super.onFocusChanged(focused, direction, previouslyFocusedRect); 100 101 if (focused && imm != null && imm.isActive(this)) { 102 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); 103 } 104 } 105 106 @Override 107 public boolean onTouchEvent(MotionEvent event) { 108 boolean handled = super.onTouchEvent(event); 109 110 if (imm != null && imm.isActive(this)) { 111 imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); 112 } 113 114 return handled; 115 } 116 } 117 118 119 private Handler mHandler = new Handler() { 120 @Override 121 public void handleMessage(Message msg) { 122 switch (msg.what) { 123 case UPDATE_PROGRESS: 124 updateProgress(); 125 break; 126 127 case COOLDOWN: 128 cooldown(); 129 break; 130 } 131 } 132 }; 133 134 @Override 135 public void onCreate(Bundle savedInstanceState) { 136 super.onCreate(savedInstanceState); 137 138 // If we are not encrypted or encrypting, get out quickly. 139 String state = SystemProperties.get("vold.decrypt"); 140 if ("".equals(state) || DECRYPT_STATE.equals(state)) { 141 // Disable the crypt keeper. 142 PackageManager pm = getPackageManager(); 143 ComponentName name = new ComponentName(this, CryptKeeper.class); 144 pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0); 145 return; 146 } 147 148 // Disable the status bar 149 StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE); 150 sbm.disable(StatusBarManager.DISABLE_EXPAND 151 | StatusBarManager.DISABLE_NOTIFICATION_ICONS 152 | StatusBarManager.DISABLE_NOTIFICATION_ALERTS 153 | StatusBarManager.DISABLE_SYSTEM_INFO 154 | StatusBarManager.DISABLE_NAVIGATION 155 | StatusBarManager.DISABLE_BACK); 156 157 // Check for (and recover) retained instance data 158 Object lastInstance = getLastNonConfigurationInstance(); 159 if (lastInstance instanceof NonConfigurationInstanceState) { 160 NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance; 161 mWakeLock = retained.wakelock; 162 } 163 } 164 165 /** 166 * Note, we defer the state check and screen setup to onStart() because this will be 167 * re-run if the user clicks the power button (sleeping/waking the screen), and this is 168 * especially important if we were to lose the wakelock for any reason. 169 */ 170 @Override 171 public void onStart() { 172 super.onStart(); 173 174 // Check to see why we were started. 175 String progress = SystemProperties.get("vold.encrypt_progress"); 176 if (!"".equals(progress)) { 177 setContentView(R.layout.crypt_keeper_progress); 178 encryptionProgressInit(); 179 } else { 180 setContentView(R.layout.crypt_keeper_password_entry); 181 passwordEntryInit(); 182 } 183 } 184 185 @Override 186 public void onStop() { 187 super.onStop(); 188 189 mHandler.removeMessages(COOLDOWN); 190 mHandler.removeMessages(UPDATE_PROGRESS); 191 } 192 193 /** 194 * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop() 195 * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears 196 * mWakeLock so the subsequent call to onDestroy does not release it. 197 */ 198 @Override 199 public Object onRetainNonConfigurationInstance() { 200 NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock); 201 mWakeLock = null; 202 return state; 203 } 204 205 @Override 206 public void onDestroy() { 207 super.onDestroy(); 208 209 if (mWakeLock != null) { 210 mWakeLock.release(); 211 mWakeLock = null; 212 } 213 } 214 215 private void encryptionProgressInit() { 216 // Accquire a partial wakelock to prevent the device from sleeping. Note 217 // we never release this wakelock as we will be restarted after the device 218 // is encrypted. 219 220 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 221 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); 222 223 mWakeLock.acquire(); 224 225 ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); 226 progressBar.setIndeterminate(true); 227 228 updateProgress(); 229 } 230 231 private void showFactoryReset() { 232 // Hide the encryption-bot to make room for the "factory reset" button 233 findViewById(R.id.encroid).setVisibility(View.GONE); 234 235 // Show the reset button, failure text, and a divider 236 Button button = (Button) findViewById(R.id.factory_reset); 237 button.setVisibility(View.VISIBLE); 238 button.setOnClickListener(new OnClickListener() { 239 public void onClick(View v) { 240 // Factory reset the device. 241 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); 242 } 243 }); 244 245 TextView tv = (TextView) findViewById(R.id.title); 246 tv.setText(R.string.crypt_keeper_failed_title); 247 248 tv = (TextView) findViewById(R.id.status); 249 tv.setText(R.string.crypt_keeper_failed_summary); 250 251 View view = findViewById(R.id.bottom_divider); 252 view.setVisibility(View.VISIBLE); 253 } 254 255 private void updateProgress() { 256 String state = SystemProperties.get("vold.encrypt_progress"); 257 258 if ("error_partially_encrypted".equals(state)) { 259 showFactoryReset(); 260 return; 261 } 262 263 int progress = 0; 264 try { 265 progress = Integer.parseInt(state); 266 } catch (Exception e) { 267 Log.w(TAG, "Error parsing progress: " + e.toString()); 268 } 269 270 CharSequence status = getText(R.string.crypt_keeper_setup_description); 271 TextView tv = (TextView) findViewById(R.id.status); 272 tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress))); 273 274 // Check the progress every 5 seconds 275 mHandler.removeMessages(UPDATE_PROGRESS); 276 mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000); 277 } 278 279 private void cooldown() { 280 TextView tv = (TextView) findViewById(R.id.status); 281 282 if (mCooldown <= 0) { 283 // Re-enable the password entry 284 EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry); 285 passwordEntry.setEnabled(true); 286 287 tv.setVisibility(View.GONE); 288 } else { 289 CharSequence template = getText(R.string.crypt_keeper_cooldown); 290 tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown))); 291 292 tv.setVisibility(View.VISIBLE); 293 294 mCooldown--; 295 mHandler.removeMessages(COOLDOWN); 296 mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second 297 } 298 } 299 300 private void passwordEntryInit() { 301 TextView passwordEntry = (TextView) findViewById(R.id.passwordEntry); 302 passwordEntry.setOnEditorActionListener(this); 303 304 KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); 305 306 PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this, 307 keyboardView, passwordEntry, false); 308 keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); 309 } 310 311 private IMountService getMountService() { 312 IBinder service = ServiceManager.getService("mount"); 313 if (service != null) { 314 return IMountService.Stub.asInterface(service); 315 } 316 return null; 317 } 318 319 @Override 320 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 321 if (actionId == EditorInfo.IME_NULL) { 322 // Get the password 323 String password = v.getText().toString(); 324 325 if (TextUtils.isEmpty(password)) { 326 return true; 327 } 328 329 // Now that we have the password clear the password field. 330 v.setText(null); 331 332 IMountService service = getMountService(); 333 try { 334 int failedAttempts = service.decryptStorage(password); 335 336 if (failedAttempts == 0) { 337 // The password was entered successfully. Start the Blank activity 338 // so this activity animates to black before the devices starts. Note 339 // It has 1 second to complete the animation or it will be frozen 340 // until the boot animation comes back up. 341 Intent intent = new Intent(this, Blank.class); 342 finish(); 343 startActivity(intent); 344 } else if (failedAttempts == MAX_FAILED_ATTEMPTS) { 345 // Factory reset the device. 346 sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); 347 } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) { 348 mCooldown = COOL_DOWN_INTERVAL; 349 EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry); 350 passwordEntry.setEnabled(false); 351 cooldown(); 352 } else { 353 TextView tv = (TextView) findViewById(R.id.status); 354 tv.setText(R.string.try_again); 355 tv.setVisibility(View.VISIBLE); 356 } 357 } catch (Exception e) { 358 Log.e(TAG, "Error while decrypting...", e); 359 } 360 361 return true; 362 } 363 return false; 364 } 365}