Notify user when waiting for decryption password

1. Disable back presses from physical keyboard during encryption: Fix b/6139810
2. Keep screen on when waiting for password. Fix b/6153213 and b/6149606
3. Alert the user with sound when waiting for password. Fix b/6149606
4. Add debugging feature to display the password screen without having to reboot the device.

Change-Id: I588aa7d96e1140f95a6fa91e0281117907f666f7
This commit is contained in:
Vikram Aggarwal
2012-05-01 15:37:30 -07:00
parent 7706a2a143
commit de3c9cb412

View File

@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@@ -68,10 +69,14 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
private static final String TAG = "CryptKeeper"; private static final String TAG = "CryptKeeper";
private static final String DECRYPT_STATE = "trigger_restart_framework"; private static final String DECRYPT_STATE = "trigger_restart_framework";
/** Message sent to us to indicate encryption update progress. */
private static final int MESSAGE_UPDATE_PROGRESS = 1;
/** Message sent to us to cool-down (waste user's time between password attempts) */
private static final int MESSAGE_COOLDOWN = 2;
/** Message sent to us to indicate alerting the user that we are waiting for password entry */
private static final int MESSAGE_NOTIFY = 3;
private static final int UPDATE_PROGRESS = 1; // Constants used to control policy.
private static final int COOLDOWN = 2;
private static final int MAX_FAILED_ATTEMPTS = 30; private static final int MAX_FAILED_ATTEMPTS = 30;
private static final int COOL_DOWN_ATTEMPTS = 10; private static final int COOL_DOWN_ATTEMPTS = 10;
private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
@@ -84,12 +89,15 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
"com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW"; "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW";
private static final String FORCE_VIEW_PROGRESS = "progress"; private static final String FORCE_VIEW_PROGRESS = "progress";
private static final String FORCE_VIEW_ERROR = "error"; private static final String FORCE_VIEW_ERROR = "error";
private static final String FORCE_VIEW_PASSWORD = "password";
/** When encryption is detected, this flag indicates whether or not we've checked for errors. */ /** When encryption is detected, this flag indicates whether or not we've checked for errors. */
private boolean mValidationComplete; private boolean mValidationComplete;
private boolean mValidationRequested; private boolean mValidationRequested;
/** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */ /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
private boolean mEncryptionGoneBad; private boolean mEncryptionGoneBad;
/** A flag to indicate when the back event should be ignored */
private boolean mIgnoreBack = false;
private int mCooldown; private int mCooldown;
PowerManager.WakeLock mWakeLock; PowerManager.WakeLock mWakeLock;
@@ -115,12 +123,17 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.crypt_keeper_blank); setContentView(R.layout.crypt_keeper_blank);
} }
/** Ignore all back events. */
@Override
public void onBackPressed() {
return;
}
} }
private class DecryptTask extends AsyncTask<String, Void, Integer> { private class DecryptTask extends AsyncTask<String, Void, Integer> {
@Override @Override
protected Integer doInBackground(String... params) { protected Integer doInBackground(String... params) {
IMountService service = getMountService(); final IMountService service = getMountService();
try { try {
return service.decryptStorage(params[0]); return service.decryptStorage(params[0]);
} catch (Exception e) { } catch (Exception e) {
@@ -159,7 +172,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
private class ValidationTask extends AsyncTask<Void, Void, Boolean> { private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
@Override @Override
protected Boolean doInBackground(Void... params) { protected Boolean doInBackground(Void... params) {
IMountService service = getMountService(); final IMountService service = getMountService();
try { try {
Log.d(TAG, "Validating encryption state."); Log.d(TAG, "Validating encryption state.");
int state = service.getEncryptionState(); int state = service.getEncryptionState();
@@ -191,17 +204,23 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case UPDATE_PROGRESS: case MESSAGE_UPDATE_PROGRESS:
updateProgress(); updateProgress();
break; break;
case COOLDOWN: case MESSAGE_COOLDOWN:
cooldown(); cooldown();
break; break;
case MESSAGE_NOTIFY:
notifyUser();
break;
} }
} }
}; };
private AudioManager mAudioManager;
/** @return whether or not this Activity was started for debugging the UI only. */ /** @return whether or not this Activity was started for debugging the UI only. */
private boolean isDebugView() { private boolean isDebugView() {
return getIntent().hasExtra(EXTRA_FORCE_VIEW); return getIntent().hasExtra(EXTRA_FORCE_VIEW);
@@ -212,12 +231,45 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW)); return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
} }
/**
* Notify the user that we are awaiting input. Currently this sends an audio alert.
*/
private void notifyUser() {
Log.d(TAG, "Notifying user that we are waiting for input...");
if (mAudioManager != null) {
try {
// Play the standard keypress sound at full volume. This should be available on
// every device. We cannot play a ringtone here because media services aren't
// available yet. A DTMF-style tone is too soft to be noticed, and might not exist
// on tablet devices. The idea is to alert the user that something is needed: this
// does not have to be pleasing.
mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100);
} catch (Exception e) {
Log.w(TAG, "notifyUser: Exception while playing sound: " + e);
}
}
// Notify the user again in 30 seconds.
mHandler.removeMessages(MESSAGE_NOTIFY);
mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 30 * 1000);
}
/**
* Ignore back events after the user has entered the decrypt screen and while the device is
* encrypting.
*/
@Override
public void onBackPressed() {
if (mIgnoreBack)
return;
super.onBackPressed();
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// If we are not encrypted or encrypting, get out quickly. // If we are not encrypted or encrypting, get out quickly.
String state = SystemProperties.get("vold.decrypt"); final String state = SystemProperties.get("vold.decrypt");
if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) { if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
// Disable the crypt keeper. // Disable the crypt keeper.
PackageManager pm = getPackageManager(); PackageManager pm = getPackageManager();
@@ -245,8 +297,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
| StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_HOME
| StatusBarManager.DISABLE_RECENT); | StatusBarManager.DISABLE_RECENT);
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Check for (and recover) retained instance data // Check for (and recover) retained instance data
Object lastInstance = getLastNonConfigurationInstance(); final Object lastInstance = getLastNonConfigurationInstance();
if (lastInstance instanceof NonConfigurationInstanceState) { if (lastInstance instanceof NonConfigurationInstanceState) {
NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance; NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
mWakeLock = retained.wakelock; mWakeLock = retained.wakelock;
@@ -262,7 +315,6 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
setupUi(); setupUi();
} }
@@ -277,11 +329,11 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
return; return;
} }
String progress = SystemProperties.get("vold.encrypt_progress"); final String progress = SystemProperties.get("vold.encrypt_progress");
if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) { if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
setContentView(R.layout.crypt_keeper_progress); setContentView(R.layout.crypt_keeper_progress);
encryptionProgressInit(); encryptionProgressInit();
} else if (mValidationComplete) { } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
setContentView(R.layout.crypt_keeper_password_entry); setContentView(R.layout.crypt_keeper_password_entry);
passwordEntryInit(); passwordEntryInit();
} else if (!mValidationRequested) { } else if (!mValidationRequested) {
@@ -294,8 +346,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
mHandler.removeMessages(COOLDOWN); mHandler.removeMessages(MESSAGE_COOLDOWN);
mHandler.removeMessages(UPDATE_PROGRESS); mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
mHandler.removeMessages(MESSAGE_NOTIFY);
} }
/** /**
@@ -322,11 +375,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
} }
} }
/**
* Start encrypting the device.
*/
private void encryptionProgressInit() { private void encryptionProgressInit() {
// Accquire a partial wakelock to prevent the device from sleeping. Note // Accquire a partial wakelock to prevent the device from sleeping. Note
// we never release this wakelock as we will be restarted after the device // we never release this wakelock as we will be restarted after the device
// is encrypted. // is encrypted.
Log.d(TAG, "Encryption progress screen initializing."); Log.d(TAG, "Encryption progress screen initializing.");
if (mWakeLock == null) { if (mWakeLock == null) {
Log.d(TAG, "Acquiring wakelock."); Log.d(TAG, "Acquiring wakelock.");
@@ -336,6 +391,10 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
} }
((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true); ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true);
// Ignore all back presses from now, both hard and soft keys.
mIgnoreBack = true;
// Start the first run of progress manually. This method sets up messages to occur at
// repeated intervals.
updateProgress(); updateProgress();
} }
@@ -384,20 +443,22 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
final CharSequence status = getText(R.string.crypt_keeper_setup_description); final CharSequence status = getText(R.string.crypt_keeper_setup_description);
Log.v(TAG, "Encryption progress: " + progress); Log.v(TAG, "Encryption progress: " + progress);
final TextView tv = (TextView) findViewById(R.id.status); final TextView tv = (TextView) findViewById(R.id.status);
if (tv != null) {
tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress))); tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
}
// Check the progress every 5 seconds // Check the progress every 5 seconds
mHandler.removeMessages(UPDATE_PROGRESS); mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000); mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
} }
/** Disable password input for a while to force the user to waste time between retries */
private void cooldown() { private void cooldown() {
final TextView status = (TextView) findViewById(R.id.status); final TextView status = (TextView) findViewById(R.id.status);
if (mCooldown <= 0) { if (mCooldown <= 0) {
// Re-enable the password entry // Re-enable the password entry and back presses.
mPasswordEntry.setEnabled(true); mPasswordEntry.setEnabled(true);
mIgnoreBack = false;
status.setVisibility(View.GONE); status.setVisibility(View.GONE);
} else { } else {
CharSequence template = getText(R.string.crypt_keeper_cooldown); CharSequence template = getText(R.string.crypt_keeper_cooldown);
@@ -406,8 +467,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
status.setVisibility(View.VISIBLE); status.setVisibility(View.VISIBLE);
mCooldown--; mCooldown--;
mHandler.removeMessages(COOLDOWN); mHandler.removeMessages(MESSAGE_COOLDOWN);
mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second
} }
} }
@@ -429,6 +490,17 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}); });
} }
// We want to keep the screen on while waiting for input. In minimal boot mode, the device
// is completely non-functional, and we want the user to notice the device and enter a
// password.
if (mWakeLock == null) {
Log.d(TAG, "Acquiring wakelock.");
final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null) {
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWakeLock.acquire();
}
}
// Asynchronously throw up the IME, since there are issues with requesting it to be shown // Asynchronously throw up the IME, since there are issues with requesting it to be shown
// immediately. // immediately.
mHandler.postDelayed(new Runnable() { mHandler.postDelayed(new Runnable() {
@@ -438,6 +510,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}, 0); }, 0);
updateEmergencyCallButtonState(); updateEmergencyCallButtonState();
// Notify the user that we are waiting for him to enter the password to get the device
// out of this completely dead state.
notifyUser();
} }
/** /**
@@ -509,10 +584,10 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
// Now that we have the password clear the password field. // Now that we have the password clear the password field.
v.setText(null); v.setText(null);
// Disable the password entry while checking the password. This // Disable the password entry and back keypress while checking the password. These
// we either be re-enabled if the password was wrong or after the // we either be re-enabled if the password was wrong or after the cooldown period.
// cooldown period.
mPasswordEntry.setEnabled(false); mPasswordEntry.setEnabled(false);
mIgnoreBack = true;
Log.d(TAG, "Attempting to send command to decrypt"); Log.d(TAG, "Attempting to send command to decrypt");
new DecryptTask().execute(password); new DecryptTask().execute(password);