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}