CryptKeeper.java revision 00046d6e176eb05c3e3b0d13d38a8da7491bba5e
1/*
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;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.graphics.Rect;
29import android.inputmethodservice.KeyboardView;
30import android.os.AsyncTask;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.IBinder;
34import android.os.Message;
35import android.os.PowerManager;
36import android.os.ServiceManager;
37import android.os.SystemProperties;
38import android.os.storage.IMountService;
39import android.text.TextUtils;
40import android.util.AttributeSet;
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.MotionEvent;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.view.inputmethod.EditorInfo;
47import android.view.inputmethod.InputMethodManager;
48import android.widget.Button;
49import android.widget.EditText;
50import android.widget.ProgressBar;
51import android.widget.TextView;
52
53public class CryptKeeper extends Activity implements TextView.OnEditorActionListener {
54    private static final String TAG = "CryptKeeper";
55
56    private static final String DECRYPT_STATE = "trigger_restart_framework";
57
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
65    private int mCooldown;
66    PowerManager.WakeLock mWakeLock;
67    private EditText mPasswordEntry;
68
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
80    // This activity is used to fade the screen to black after the password is entered.
81    public static class Blank extends Activity {
82        @Override
83        public void onCreate(Bundle savedInstanceState) {
84            super.onCreate(savedInstanceState);
85            setContentView(R.layout.crypt_keeper_blank);
86        }
87    }
88
89    // 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
120    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    }
158
159    private Handler mHandler = new Handler() {
160        @Override
161        public void handleMessage(Message msg) {
162            switch (msg.what) {
163            case UPDATE_PROGRESS:
164                updateProgress();
165                break;
166
167            case COOLDOWN:
168                cooldown();
169                break;
170            }
171        }
172    };
173
174    @Override
175    public void onCreate(Bundle savedInstanceState) {
176        super.onCreate(savedInstanceState);
177
178        // If we are not encrypted or encrypting, get out quickly.
179        String state = SystemProperties.get("vold.decrypt");
180        if ("".equals(state) || DECRYPT_STATE.equals(state)) {
181            // Disable the crypt keeper.
182            PackageManager pm = getPackageManager();
183            ComponentName name = new ComponentName(this, CryptKeeper.class);
184            pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
185            return;
186        }
187
188        // Disable the status bar
189        StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
190        sbm.disable(StatusBarManager.DISABLE_EXPAND
191                | StatusBarManager.DISABLE_NOTIFICATION_ICONS
192                | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
193                | StatusBarManager.DISABLE_SYSTEM_INFO
194                | StatusBarManager.DISABLE_NAVIGATION
195                | StatusBarManager.DISABLE_BACK);
196
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        }
203    }
204
205    /**
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
225    @Override
226    public void onStop() {
227        super.onStop();
228
229        mHandler.removeMessages(COOLDOWN);
230        mHandler.removeMessages(UPDATE_PROGRESS);
231    }
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();
248
249        if (mWakeLock != null) {
250            mWakeLock.release();
251            mWakeLock = null;
252        }
253    }
254
255    private void encryptionProgressInit() {
256        // 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);
261        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
262
263        mWakeLock.acquire();
264
265        ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
266        progressBar.setIndeterminate(true);
267
268        updateProgress();
269    }
270
271    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
295    private void updateProgress() {
296        String state = SystemProperties.get("vold.encrypt_progress");
297
298        if ("error_partially_encrypted".equals(state)) {
299            showFactoryReset();
300            return;
301        }
302
303        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);
321
322        if (mCooldown <= 0) {
323            // Re-enable the password entry
324            mPasswordEntry.setEnabled(true);
325
326            tv.setVisibility(View.GONE);
327        } else {
328            CharSequence template = getText(R.string.crypt_keeper_cooldown);
329            tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
330
331            tv.setVisibility(View.VISIBLE);
332
333            mCooldown--;
334            mHandler.removeMessages(COOLDOWN);
335            mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
336        }
337    }
338
339    private void passwordEntryInit() {
340        mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
341        mPasswordEntry.setOnEditorActionListener(this);
342
343        KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
344
345        if (keyboardView != null) {
346            PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
347                    keyboardView, mPasswordEntry, false);
348            keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
349        }
350    }
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) {
362        if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
363            // Get the password
364            String password = v.getText().toString();
365
366            if (TextUtils.isEmpty(password)) {
367                return true;
368            }
369
370            // Now that we have the password clear the password field.
371            v.setText(null);
372
373            // 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);
377
378            new DecryptTask().execute(password);
379
380            return true;
381        }
382        return false;
383    }
384}