Files
app_Settings/src/com/android/settings/CryptKeeper.java
David Brown 8373b45169 Add "emergency call" button to CryptKeeper
This allows you to make an emergency call without needing to decrypt
your device first.

The exact appearance of the button, and the two possible icons shown to
the left of the text, are taken directly from the corresponding
framework resources (see keyguard_screen_*.xml, ic_emergency.png, and
stat_sys_phone_call.png.)

Also, the code in CryptKeeper.java for updating the state of, and
handling clicks from, the "Emergency call" button is mostly duplicated
from the corresponding code in LockPatternUtils and
LockPatternKeyguardView under frameworks/base.

Bug: 4494186
Change-Id: I36a713fdbc3281a7ba46762d47d5b61fb3cd194d
2011-06-24 16:18:30 -07:00

462 lines
16 KiB
Java

/*
* 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 com.android.internal.telephony.ITelephony;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView;
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.graphics.Rect;
import android.inputmethodservice.KeyboardView;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.storage.IMountService;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
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
// Intent action for launching the Emergency Dialer activity.
static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
private int mCooldown;
PowerManager.WakeLock mWakeLock;
private EditText mPasswordEntry;
/**
* Used to propagate state through configuration changes (e.g. screen rotation)
*/
private static class NonConfigurationInstanceState {
final PowerManager.WakeLock wakelock;
NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
wakelock = _wakelock;
}
}
// This activity is used to fade the screen to black after the password is entered.
public static class Blank extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crypt_keeper_blank);
}
}
// Use a custom EditText to prevent the input method from showing.
public static class CryptEditText extends EditText {
InputMethodManager imm;
public CryptEditText(Context context, AttributeSet attrs) {
super(context, attrs);
imm = ((InputMethodManager) getContext().
getSystemService(Context.INPUT_METHOD_SERVICE));
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if (focused && imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = super.onTouchEvent(event);
if (imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
}
return handled;
}
}
private class DecryptTask extends AsyncTask<String, Void, Integer> {
@Override
protected Integer doInBackground(String... params) {
IMountService service = getMountService();
try {
return service.decryptStorage(params[0]);
} catch (Exception e) {
Log.e(TAG, "Error while decrypting...", e);
return -1;
}
}
@Override
protected void onPostExecute(Integer failedAttempts) {
if (failedAttempts == 0) {
// The password was entered successfully. Start the Blank activity
// so this activity animates to black before the devices starts. Note
// It has 1 second to complete the animation or it will be frozen
// until the boot animation comes back up.
Intent intent = new Intent(CryptKeeper.this, Blank.class);
finish();
startActivity(intent);
} else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
// Factory reset the device.
sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
} else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
mCooldown = COOL_DOWN_INTERVAL;
cooldown();
} else {
TextView tv = (TextView) findViewById(R.id.status);
tv.setText(R.string.try_again);
tv.setVisibility(View.VISIBLE);
// Reenable the password entry
mPasswordEntry.setEnabled(true);
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_PROGRESS:
updateProgress();
break;
case COOLDOWN:
cooldown();
break;
}
}
};
@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");
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;
}
// Disable the status bar
StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
sbm.disable(StatusBarManager.DISABLE_EXPAND
| StatusBarManager.DISABLE_NOTIFICATION_ICONS
| StatusBarManager.DISABLE_NOTIFICATION_ALERTS
| StatusBarManager.DISABLE_SYSTEM_INFO
| StatusBarManager.DISABLE_NAVIGATION
| StatusBarManager.DISABLE_BACK);
// Check for (and recover) retained instance data
Object lastInstance = getLastNonConfigurationInstance();
if (lastInstance instanceof NonConfigurationInstanceState) {
NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
mWakeLock = retained.wakelock;
}
}
/**
* Note, we defer the state check and screen setup to onStart() because this will be
* re-run if the user clicks the power button (sleeping/waking the screen), and this is
* especially important if we were to lose the wakelock for any reason.
*/
@Override
public void onStart() {
super.onStart();
// Check to see why we were started.
String progress = SystemProperties.get("vold.encrypt_progress");
if (!"".equals(progress)) {
setContentView(R.layout.crypt_keeper_progress);
encryptionProgressInit();
} else {
setContentView(R.layout.crypt_keeper_password_entry);
passwordEntryInit();
}
}
@Override
public void onStop() {
super.onStop();
mHandler.removeMessages(COOLDOWN);
mHandler.removeMessages(UPDATE_PROGRESS);
}
/**
* Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
* and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
* mWakeLock so the subsequent call to onDestroy does not release it.
*/
@Override
public Object onRetainNonConfigurationInstance() {
NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
mWakeLock = null;
return state;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
}
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.
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWakeLock.acquire();
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
progressBar.setIndeterminate(true);
updateProgress();
}
private void showFactoryReset() {
// Hide the encryption-bot to make room for the "factory reset" button
findViewById(R.id.encroid).setVisibility(View.GONE);
// Show the reset button, failure text, and a divider
Button button = (Button) findViewById(R.id.factory_reset);
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Factory reset the device.
sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
}
});
TextView tv = (TextView) findViewById(R.id.title);
tv.setText(R.string.crypt_keeper_failed_title);
tv = (TextView) findViewById(R.id.status);
tv.setText(R.string.crypt_keeper_failed_summary);
View view = findViewById(R.id.bottom_divider);
view.setVisibility(View.VISIBLE);
}
private void updateProgress() {
String state = SystemProperties.get("vold.encrypt_progress");
if ("error_partially_encrypted".equals(state)) {
showFactoryReset();
return;
}
int progress = 0;
try {
progress = Integer.parseInt(state);
} catch (Exception e) {
Log.w(TAG, "Error parsing progress: " + e.toString());
}
CharSequence status = getText(R.string.crypt_keeper_setup_description);
TextView tv = (TextView) findViewById(R.id.status);
tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
// Check the progress every 5 seconds
mHandler.removeMessages(UPDATE_PROGRESS);
mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000);
}
private void cooldown() {
TextView tv = (TextView) findViewById(R.id.status);
if (mCooldown <= 0) {
// Re-enable the password entry
mPasswordEntry.setEnabled(true);
tv.setVisibility(View.GONE);
} else {
CharSequence template = getText(R.string.crypt_keeper_cooldown);
tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
tv.setVisibility(View.VISIBLE);
mCooldown--;
mHandler.removeMessages(COOLDOWN);
mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second
}
}
private void passwordEntryInit() {
mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
mPasswordEntry.setOnEditorActionListener(this);
KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
if (keyboardView != null) {
PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
keyboardView, mPasswordEntry, false);
keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
}
updateEmergencyCallButtonState();
}
private IMountService getMountService() {
IBinder service = ServiceManager.getService("mount");
if (service != null) {
return IMountService.Stub.asInterface(service);
}
return null;
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
// 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);
// Disable the password entry while checking the password. This
// we either be reenabled if the password was wrong or after the
// cooldown period.
mPasswordEntry.setEnabled(false);
new DecryptTask().execute(password);
return true;
}
return false;
}
//
// Code to update the state of, and handle clicks from, the "Emergency call" button.
//
// This code is mostly duplicated from the corresponding code in
// LockPatternUtils and LockPatternKeyguardView under frameworks/base.
//
private void updateEmergencyCallButtonState() {
Button button = (Button) findViewById(R.id.emergencyCallButton);
// The button isn't present at all in some configurations.
if (button == null) return;
if (isEmergencyCallCapable()) {
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
takeEmergencyCallAction();
}
});
} else {
button.setVisibility(View.GONE);
return;
}
int newState = TelephonyManager.getDefault().getCallState();
int textId;
if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
// show "return to call" text and show phone icon
textId = R.string.cryptkeeper_return_to_call;
int phoneCallIcon = R.drawable.stat_sys_phone_call;
button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
} else {
textId = R.string.cryptkeeper_emergency_call;
int emergencyIcon = R.drawable.ic_emergency;
button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
}
button.setText(textId);
}
private boolean isEmergencyCallCapable() {
return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
}
private void takeEmergencyCallAction() {
if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
resumeCall();
} else {
launchEmergencyDialer();
}
}
private void resumeCall() {
ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
if (phone != null) {
try {
phone.showCallScreen();
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony service: " + e);
}
}
}
private void launchEmergencyDialer() {
Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
}
}