More work on the CryptKeeper.

* There is now a 30 seconds delay after 10 failed password attempts.
* The device is factory reset after 30 failed password attempts.
* Implemented the progress UI for inplace encryption.

Change-Id: Ie830b03f9c84a117ee3048086275d6049907fa3c
This commit is contained in:
Jason parks
2011-01-18 15:28:36 -06:00
parent 3e8b76319d
commit ec5a45e79c
11 changed files with 521 additions and 5 deletions

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="60dip"
android:paddingLeft="128dip"
android:paddingRight="128dip"
android:paddingBottom="0dip"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="48dip"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="16dip"
android:textSize="30dip"
android:textColor="#ff99cc00"
android:text="@string/crypt_keeper_setup_title"
android:gravity="bottom"
/>
<!-- Divider -->
<RelativeLayout
android:id="@+id/top_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
/>
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:layout_marginLeft="16dip"
android:text="@string/crypt_keeper_setup_description"
/>
</LinearLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="12dp"
android:textSize="20sp"
android:text="@string/crypt_keeper_final_desc" />
<Button android:id="@+id/execute_encrypt"
android:layout_gravity="center_horizontal"
android:layout_marginTop="40dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/crypt_keeper_button_text"
android:gravity="center" />
</LinearLayout>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="60dip"
android:paddingLeft="128dip"
android:paddingRight="128dip"
android:paddingBottom="0dip"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="48dip"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="16dip"
android:textSize="30dip"
android:textColor="#ff99cc00"
android:text="@string/crypt_keeper_setup_title"
android:gravity="bottom"
/>
<!-- Divider -->
<RelativeLayout
android:id="@+id/top_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
/>
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:layout_marginLeft="16dip"
android:text="@string/crypt_keeper_setup_description"
/>
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="12dp"
android:layout_weight="1"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="@string/crypt_keeper_desc"
/>
</ScrollView>
<Button
android:id="@+id/initiate_encrypt"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dip"
android:layout_marginBottom="12dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/crypt_keeper_button_text"
android:gravity="center"
/>
</LinearLayout>

View File

@@ -590,6 +590,12 @@
<string name="crypt_keeper_final_desc">Encrypt user data? This operation is not reversible and may not be interrupted without loos of data! Encryption may take up to an hour.</string>
<string name="crypt_keeper_setup_title">Encrypting</string>
<string name="crypt_keeper_setup_description" product="tablet">Please wait while your tablet is being encrypted.</string>
<string name="crypt_keeper_setup_description" product="default">Please wait while your phone is being encrypted.</string>
<string name="crypt_keeper_cooldown">Try again in ^1 seconds.</string>
<!-- Unlock Picker Settings --><skip />

View File

@@ -23,37 +23,117 @@ import android.app.Activity;
import android.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.storage.IMountService;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.Date;
public class CryptKeeper extends Activity implements TextView.OnEditorActionListener {
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;
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
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_PROGRESS:
String state = SystemProperties.get("vold.encrypt_progress");
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
progressBar.setProgress(0);
try {
int progress = Integer.parseInt(state);
progressBar.setProgress(progress);
} catch (Exception e) {
Log.w(TAG, "Error parsing progress: " + e.toString());
}
// Check the status every 1 second
sendEmptyMessageDelayed(0, 1000);
break;
case COOLDOWN:
TextView tv = (TextView) findViewById(R.id.status);
if (mCooldown <= 0) {
// Re-enable the password entry
EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry);
passwordEntry.setEnabled(true);
tv.setText(R.string.try_again);
} else {
CharSequence tempalte = getText(R.string.crypt_keeper_cooldown);
tv.setText(TextUtils.expandTemplate(tempalte, Integer.toString(mCooldown)));
mCooldown--;
mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
}
break;
}
}
};
private int mFailedAttempts = 0;
private int mCooldown;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crypt_keeper);
String state = SystemProperties.get("vold.decrypt");
if ("".equals(state) || DECRYPT_STATE.equals(state)) {
// Disable the crypt keeper.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, CryptKeeper.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
return;
}
// Check to see why we were started.
String progress = SystemProperties.get("vold.encrypt_progress");
if ("startup".equals(progress)) {
setContentView(R.layout.crypt_keeper_progress);
encryptionProgressInit();
} else {
setContentView(R.layout.crypt_keeper_password_entry);
passwordEntryInit();
}
}
private void encryptionProgressInit() {
mHandler.sendEmptyMessage(UPDATE_PROGRESS);
}
private void passwordEntryInit() {
TextView passwordEntry = (TextView) findViewById(R.id.passwordEntry);
passwordEntry.setOnEditorActionListener(this);
@@ -92,6 +172,10 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
// Get the password
String password = v.getText().toString();
if (TextUtils.isEmpty(password)) {
return true;
}
// Now that we have the password clear the password field.
v.setText(null);
@@ -102,11 +186,22 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
// For now the only way to get here is for the password to be
// wrong.
mFailedAttempts++;
if (mFailedAttempts == MAX_FAILED_ATTEMPTS) {
// Factory reset the device.
sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
} else if ((mFailedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
mCooldown = COOL_DOWN_INTERVAL;
EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry);
passwordEntry.setEnabled(false);
mHandler.sendEmptyMessage(COOLDOWN);
} else {
TextView tv = (TextView) findViewById(R.id.status);
tv.setText(R.string.try_again);
}
} catch (Exception e) {
Log.e("CryptKeeper", "Error while decrypting...", e);
Log.e(TAG, "Error while decrypting...", e);
}
return true;

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.app.Fragment;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.storage.IMountService;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
public class CryptKeeperComfirm extends Fragment {
private View mContentView;
private Button mFinalButton;
private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
public void onClick(View v) {
if (Utils.isMonkeyRunning()) {
return;
}
IBinder service = ServiceManager.getService("mount");
if (service == null) {
return;
}
IMountService mountService = IMountService.Stub.asInterface(service);
try {
Bundle args = getArguments();
mountService.encryptStorage(args.getString("password"));
} catch (Exception e) {
Log.e("CryptKeeper", "Error while encrypting...", e);
}
}
};
private void establishFinalConfirmationState() {
mFinalButton = (Button) mContentView.findViewById(R.id.execute_encrypt);
mFinalButton.setOnClickListener(mFinalClickListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mContentView = inflater.inflate(R.layout.crypt_keeper_confirm, null);
establishFinalConfirmationState();
return mContentView;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
/**
* Confirm and execute a reset of the device to a clean "just out of the box"
* state. Multiple confirmations are required: first, a general "are you sure
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
* has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
* ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
* locked, et cetera, then the confirmation sequence is abandoned.
*
* This is the initial screen.
*/
public class CryptKeeperSettings extends Fragment {
private static final String TAG = "CryptKeeper";
private static final int KEYGUARD_REQUEST = 55;
private View mContentView;
private Button mInitiateButton;
/**
* Keyguard validation is run using the standard {@link ConfirmLockPattern}
* component as a subactivity
* @param request the request code to be returned once confirmation finishes
* @return true if confirmation launched
*/
private boolean runKeyguardConfirmation(int request) {
Resources res = getActivity().getResources();
return new ChooseLockSettingsHelper(getActivity(), this)
.launchConfirmationActivity(request,
res.getText(R.string.master_clear_gesture_prompt),
res.getText(R.string.master_clear_gesture_explanation));
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != KEYGUARD_REQUEST) {
return;
}
// If the user entered a valid keyguard trace, present the final
// confirmation prompt; otherwise, go back to the initial state.
if (resultCode == Activity.RESULT_OK) {
String password = data.getStringExtra("password");
showFinalConfirmation(password);
} else {
establishInitialState();
}
}
private void showFinalConfirmation(String password) {
Preference preference = new Preference(getActivity());
preference.setFragment(CryptKeeperComfirm.class.getName());
preference.setTitle(R.string.crypt_keeper_confirm_title);
preference.getExtras().putString("password", password);
((PreferenceActivity) getActivity()).onPreferenceStartFragment(null, preference);
}
/**
* If the user clicks to begin the reset sequence, we next require a
* keyguard confirmation if the user has currently enabled one. If there
* is no keyguard available, we simply go to the final confirmation prompt.
*/
private Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
public void onClick(View v) {
if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
// TODO: Need to request a password
// showFinalConfirmation();
}
}
};
/**
* In its initial state, the activity presents a button for the user to
* click in order to initiate a confirmation sequence. This method is
* called from various other points in the code to reset the activity to
* this base state.
*
* <p>Reinflating views from resources is expensive and prevents us from
* caching widget pointers, so we use a single-inflate pattern: we lazy-
* inflate each view, caching all of the widget pointers we'll need at the
* time, then simply reuse the inflated views directly whenever we need
* to change contents.
*/
private void establishInitialState() {
mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_encrypt);
mInitiateButton.setOnClickListener(mInitiateListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
mContentView = inflater.inflate(R.layout.crypt_keeper_settings, null);
establishInitialState();
return mContentView;
}
}