OSDN Git Service

Notify user when waiting for decryption password
authorVikram Aggarwal <viki@google.com>
Tue, 1 May 2012 22:37:30 +0000 (15:37 -0700)
committerVikram Aggarwal <viki@google.com>
Tue, 1 May 2012 23:05:44 +0000 (16:05 -0700)
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

src/com/android/settings/CryptKeeper.java

index 3f35fa8..75dd565 100644 (file)
@@ -22,6 +22,7 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
 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 DECRYPT_STATE = "trigger_restart_framework";
-
-    private static final int UPDATE_PROGRESS = 1;
-    private static final int COOLDOWN = 2;
-
+    /** 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;
+
+    // Constants used to control policy.
     private static final int MAX_FAILED_ATTEMPTS = 30;
     private static final int COOL_DOWN_ATTEMPTS = 10;
     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";
     private static final String FORCE_VIEW_PROGRESS = "progress";
     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. */
     private boolean mValidationComplete;
     private boolean mValidationRequested;
     /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
     private boolean mEncryptionGoneBad;
+    /** A flag to indicate when the back event should be ignored */
+    private boolean mIgnoreBack = false;
 
     private int mCooldown;
     PowerManager.WakeLock mWakeLock;
@@ -115,12 +123,17 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
             super.onCreate(savedInstanceState);
             setContentView(R.layout.crypt_keeper_blank);
         }
+        /** Ignore all back events. */
+        @Override
+        public void onBackPressed() {
+            return;
+        }
     }
 
     private class DecryptTask extends AsyncTask<String, Void, Integer> {
         @Override
         protected Integer doInBackground(String... params) {
-            IMountService service = getMountService();
+            final IMountService service = getMountService();
             try {
                 return service.decryptStorage(params[0]);
             } catch (Exception e) {
@@ -159,7 +172,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
     private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
         @Override
         protected Boolean doInBackground(Void... params) {
-            IMountService service = getMountService();
+            final IMountService service = getMountService();
             try {
                 Log.d(TAG, "Validating encryption state.");
                 int state = service.getEncryptionState();
@@ -191,17 +204,23 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case UPDATE_PROGRESS:
+            case MESSAGE_UPDATE_PROGRESS:
                 updateProgress();
                 break;
 
-            case COOLDOWN:
+            case MESSAGE_COOLDOWN:
                 cooldown();
                 break;
+
+            case MESSAGE_NOTIFY:
+                notifyUser();
+                break;
             }
         }
     };
 
+    private AudioManager mAudioManager;
+
     /** @return whether or not this Activity was started for debugging the UI only. */
     private boolean isDebugView() {
         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));
     }
 
+    /**
+     * 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
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         // 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))) {
             // Disable the crypt keeper.
             PackageManager pm = getPackageManager();
@@ -245,8 +297,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
                 | StatusBarManager.DISABLE_HOME
                 | StatusBarManager.DISABLE_RECENT);
 
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         // Check for (and recover) retained instance data
-        Object lastInstance = getLastNonConfigurationInstance();
+        final Object lastInstance = getLastNonConfigurationInstance();
         if (lastInstance instanceof NonConfigurationInstanceState) {
             NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
             mWakeLock = retained.wakelock;
@@ -262,7 +315,6 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
     @Override
     public void onStart() {
         super.onStart();
-
         setupUi();
     }
 
@@ -277,11 +329,11 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
             return;
         }
 
-        String progress = SystemProperties.get("vold.encrypt_progress");
+        final String progress = SystemProperties.get("vold.encrypt_progress");
         if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
             setContentView(R.layout.crypt_keeper_progress);
             encryptionProgressInit();
-        } else if (mValidationComplete) {
+        } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
             setContentView(R.layout.crypt_keeper_password_entry);
             passwordEntryInit();
         } else if (!mValidationRequested) {
@@ -294,8 +346,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
     @Override
     public void onStop() {
         super.onStop();
-        mHandler.removeMessages(COOLDOWN);
-        mHandler.removeMessages(UPDATE_PROGRESS);
+        mHandler.removeMessages(MESSAGE_COOLDOWN);
+        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() {
         // Accquire a partial wakelock to prevent the device from sleeping. Note
         // we never release this wakelock as we will be restarted after the device
         // is encrypted.
-
         Log.d(TAG, "Encryption progress screen initializing.");
         if (mWakeLock == null) {
             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);
+        // 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();
     }
 
@@ -384,20 +443,22 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
         final CharSequence status = getText(R.string.crypt_keeper_setup_description);
         Log.v(TAG, "Encryption progress: " + progress);
         final TextView tv = (TextView) findViewById(R.id.status);
-        tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
-
+        if (tv != null) {
+            tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
+        }
         // Check the progress every 5 seconds
-        mHandler.removeMessages(UPDATE_PROGRESS);
-        mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000);
+        mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
+        mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
     }
 
+    /** Disable password input for a while to force the user to waste time between retries */
     private void cooldown() {
         final TextView status = (TextView) findViewById(R.id.status);
 
         if (mCooldown <= 0) {
-            // Re-enable the password entry
+            // Re-enable the password entry and back presses.
             mPasswordEntry.setEnabled(true);
-
+            mIgnoreBack = false;
             status.setVisibility(View.GONE);
         } else {
             CharSequence template = getText(R.string.crypt_keeper_cooldown);
@@ -406,8 +467,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
             status.setVisibility(View.VISIBLE);
 
             mCooldown--;
-            mHandler.removeMessages(COOLDOWN);
-            mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
+            mHandler.removeMessages(MESSAGE_COOLDOWN);
+            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
         // immediately.
         mHandler.postDelayed(new Runnable() {
@@ -438,6 +510,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
         }, 0);
 
         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.
             v.setText(null);
 
-            // Disable the password entry while checking the password. This
-            // we either be re-enabled if the password was wrong or after the
-            // cooldown period.
+            // 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 cooldown period.
             mPasswordEntry.setEnabled(false);
+            mIgnoreBack = true;
 
             Log.d(TAG, "Attempting to send command to decrypt");
             new DecryptTask().execute(password);