summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/applications/LockPatternActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/applications/LockPatternActivity.java')
-rw-r--r--src/com/android/settings/applications/LockPatternActivity.java466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/com/android/settings/applications/LockPatternActivity.java b/src/com/android/settings/applications/LockPatternActivity.java
new file mode 100644
index 0000000..05d30ec
--- /dev/null
+++ b/src/com/android/settings/applications/LockPatternActivity.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.applications;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.settings.R;
+import com.android.settings.cyanogenmod.ProtectedAccountView;
+import com.android.settings.cyanogenmod.ProtectedAccountView.OnNotifyAccountReset;
+import com.android.settings.fingerprint.FingerprintUiHelper;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+public class LockPatternActivity extends Activity implements OnNotifyAccountReset, FingerprintUiHelper.Callback {
+ public static final String PATTERN_LOCK_PROTECTED_APPS = "pattern_lock_protected_apps";
+ public static final String RECREATE_PATTERN = "recreate_pattern_lock";
+
+ private static final String STATE_IS_ACCOUNT_VIEW = "isAccountView";
+ private static final String STATE_CONTINUE_ENABLED = "continueEnabled";
+ private static final String STATE_CONFIRMING = "confirming";
+ private static final String STATE_RETRY_PATTERN = "retrypattern";
+ private static final String STATE_RETRY = "retry";
+ private static final String STATE_PATTERN_HASH = "pattern_hash";
+ private static final String STATE_CREATE = "create";
+
+ private static String TIMEOUT_PREF_KEY = "retry_timeout";
+
+ private static final int MIN_PATTERN_SIZE = 4;
+ private static final int MAX_PATTERN_RETRY = 5;
+ private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+ private static final long FAILED_ATTEMPT_RETRY = 30;
+
+ private static final int MENU_RESET = 0;
+
+ LockPatternView mLockPatternView;
+ ProtectedAccountView mAccountView;
+ ImageView mFingerprintIconView;
+
+ TextView mPatternLockHeader;
+ MenuItem mItem;
+ Button mCancel;
+ Button mContinue;
+ byte[] mPatternHash;
+
+ int mRetry = 0;
+
+ boolean mCreate;
+ boolean mRetryPattern = true;
+ boolean mConfirming = false;
+ boolean mFingerPrintSetUp = false;
+ boolean mRetryLocked = false;
+
+ private FingerprintManager mFingerprintManager;
+ private FingerprintUiHelper mFingerPrintUiHelper;
+
+ Runnable mCancelPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ mContinue.setEnabled(false);
+
+ if (mCreate) {
+ if (mConfirming) {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_need_to_confirm));
+ } else {
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_recording_intro_header));
+ mCancel.setText(getResources().getString(R.string.cancel));
+ }
+ } else {
+ mPatternLockHeader.setText(mFingerPrintSetUp ?
+ getResources().getString(R.string.pa_pattern_or_fingerprint_header)
+ : getResources().getString(R.string.lockpattern_settings_enable_summary));
+ }
+ }
+ };
+
+ View.OnClickListener mCancelOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCreate && !mConfirming && !mRetryPattern) {
+ // Retry
+ mRetryPattern = true;
+ resetPatternState(true);
+ return;
+ }
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ };
+
+ View.OnClickListener mContinueOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Button btn = (Button) v;
+ if (mConfirming) {
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(PATTERN_LOCK_PROTECTED_APPS,
+ Base64.encodeToString(mPatternHash, Base64.DEFAULT));
+ editor.commit();
+ setResult(RESULT_OK);
+ finish();
+ } else {
+ mConfirming = true;
+ mCancel.setText(getResources().getString(R.string.cancel));
+ mLockPatternView.clearPattern();
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_confirm));
+ btn.setText(getResources().getString(R.string.lockpattern_confirm_button_text));
+ btn.setEnabled(false);
+ }
+ }
+ };
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.clear();
+ menu.add(0, MENU_RESET, 0, R.string.lockpattern_reset_button)
+ .setIcon(R.drawable.ic_lockscreen_ime_white)
+ .setAlphabeticShortcut('r')
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ mItem = menu.findItem(0);
+ if (mRetryLocked) {
+ mItem.setIcon(R.drawable.ic_settings_lockscreen_white);
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(STATE_IS_ACCOUNT_VIEW, mAccountView.getVisibility() == View.VISIBLE);
+ outState.putBoolean(STATE_CONTINUE_ENABLED, mContinue.isEnabled());
+ outState.putBoolean(STATE_CONFIRMING, mConfirming);
+ outState.putBoolean(STATE_RETRY_PATTERN, mRetryPattern);
+ outState.putInt(STATE_RETRY, mRetry);
+ outState.putByteArray(STATE_PATTERN_HASH, mPatternHash);
+ outState.putBoolean(STATE_CREATE, mCreate);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ if (savedInstanceState.getBoolean(STATE_IS_ACCOUNT_VIEW)) {
+ switchToAccount();
+ } else {
+ switchToPattern(false);
+ mPatternHash = savedInstanceState.getByteArray(STATE_PATTERN_HASH);
+ mConfirming = savedInstanceState.getBoolean(STATE_CONFIRMING);
+ mRetryPattern = savedInstanceState.getBoolean(STATE_RETRY_PATTERN);
+ mRetry = savedInstanceState.getInt(STATE_RETRY);
+ mCreate = savedInstanceState.getBoolean(STATE_CREATE);
+ mContinue.setEnabled(savedInstanceState.getBoolean(STATE_CONTINUE_ENABLED,
+ mContinue.isEnabled()));
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ if (mAccountView.getVisibility() == View.VISIBLE) {
+ switchToPattern(false);
+ } else {
+ switchToAccount();
+ }
+ return true;
+ case android.R.id.home:
+ setResult(RESULT_CANCELED);
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onNotifyAccountReset() {
+ switchToPattern(true);
+ }
+
+ private void switchToPattern(boolean reset) {
+ if (isRetryLocked()) {
+ return;
+ }
+ if (reset) {
+ resetPatternState(false);
+ }
+ mPatternLockHeader.setText(mFingerPrintSetUp ?
+ getResources().getString(R.string.pa_pattern_or_fingerprint_header)
+ : getResources().getString(R.string.lockpattern_settings_enable_summary));
+ mItem.setIcon(R.drawable.ic_lockscreen_ime_white);
+ mAccountView.clearFocusOnInput();
+ mAccountView.setVisibility(View.GONE);
+ mLockPatternView.setVisibility(View.VISIBLE);
+ }
+
+ private void switchToAccount() {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_settings_reset_summary));
+ if (mItem != null) {
+ mItem.setIcon(R.drawable.ic_settings_lockscreen_white);
+ }
+ mAccountView.setVisibility(View.VISIBLE);
+ mLockPatternView.setVisibility(View.GONE);
+ }
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.patternlock);
+
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ mPatternLockHeader = (TextView) findViewById(R.id.pattern_lock_header);
+ mCancel = (Button) findViewById(R.id.pattern_lock_btn_cancel);
+ mCancel.setOnClickListener(mCancelOnClickListener);
+ mContinue = (Button) findViewById(R.id.pattern_lock_btn_continue);
+ mContinue.setOnClickListener(mContinueOnClickListener);
+
+ mAccountView = (ProtectedAccountView) findViewById(R.id.lock_account_view);
+ mAccountView.setOnNotifyAccountResetCb(this);
+ mLockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view);
+ mFingerprintIconView = (ImageView) findViewById(R.id.protected_apps_fingerprint_icon);
+
+ resetPatternState(false);
+
+ //Setup Pattern Lock View
+ mLockPatternView.setFocusable(false);
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+
+ mFingerprintManager = (FingerprintManager) getSystemService(FingerprintManager.class);
+
+ if (mFingerprintManager.isHardwareDetected()) {
+ if (mFingerprintManager.hasEnrolledFingerprints() && !mCreate) {
+ mFingerPrintSetUp = true;
+ mFingerPrintUiHelper =
+ new FingerprintUiHelper(mFingerprintIconView, mPatternLockHeader, this);
+ mFingerPrintUiHelper.setDarkIconography(true);
+ mFingerPrintUiHelper.setIdleText(getString(
+ R.string.pa_pattern_or_fingerprint_header));
+ } else {
+ mFingerPrintSetUp = false;
+ }
+ }
+ }
+
+ private void resetPatternState(boolean clear) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ String pattern = prefs.getString(PATTERN_LOCK_PROTECTED_APPS, null);
+ mCreate = pattern == null || RECREATE_PATTERN.equals(getIntent().getAction())
+ || clear;
+
+ mPatternHash = null;
+ if (pattern != null) {
+ mPatternHash = Base64.decode(pattern, Base64.DEFAULT);
+ }
+
+ mContinue.setEnabled(!mCreate);
+ mCancel.setVisibility(mCreate ? View.VISIBLE : View.GONE);
+ mCancel.setText(getResources().getString(R.string.cancel));
+ mContinue.setVisibility(mCreate ? View.VISIBLE : View.GONE);
+ mPatternLockHeader.setText(mCreate ?
+ getResources().getString(R.string.lockpattern_recording_intro_header)
+ : (mFingerPrintSetUp ?
+ getResources().getString(R.string.pa_pattern_or_fingerprint_header)
+ : getResources().getString(R.string.lockpattern_settings_enable_summary)));
+ mLockPatternView.clearPattern();
+
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onAuthenticated() {
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ @Override
+ public void onFingerprintIconVisibilityChanged(boolean visible) {
+
+ }
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+
+ mPatternLockHeader.setText(getResources().getText(
+ R.string.lockpattern_recording_inprogress));
+ mContinue.setEnabled(false);
+ }
+
+ public void onPatternCleared() {
+ }
+
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ //Check inserted Pattern
+ if (mCreate) {
+ if (pattern.size() < MIN_PATTERN_SIZE) {
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_recording_incorrect_too_short,
+ LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
+
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ mCancel.setText(getResources()
+ .getString(R.string.lockpattern_retry_button_text));
+ mRetryPattern = false;
+ return;
+ }
+
+ if (mConfirming) {
+ if (Arrays.equals(mPatternHash, patternToHash(pattern))) {
+ mContinue.setText(getResources()
+ .getString(R.string.lockpattern_confirm_button_text));
+ mContinue.setEnabled(true);
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_pattern_confirmed_header));
+ } else {
+ mContinue.setEnabled(false);
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_unlock_wrong));
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable,
+ PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ } else {
+ //Save pattern, user needs to redraw to confirm
+ mCancel.setText(getResources()
+ .getString(R.string.lockpattern_retry_button_text));
+ mRetryPattern = false;
+
+ mPatternHash = patternToHash(pattern);
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_pattern_entered_header));
+ mContinue.setEnabled(true);
+ }
+ } else {
+ //Check against existing pattern
+ if (Arrays.equals(mPatternHash, patternToHash(pattern))) {
+ setResult(RESULT_OK);
+ finish();
+ } else {
+ mRetry++;
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_unlock_wrong));
+
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+
+ if (mRetry >= MAX_PATTERN_RETRY) {
+ setPatternTimeout();
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ Toast.makeText(getApplicationContext(),
+ getResources().getString(
+ R.string.lockpattern_too_many_failed_confirmation_attempts,
+ FAILED_ATTEMPT_RETRY),
+ Toast.LENGTH_SHORT).show();
+ switchToAccount();
+ }
+ }
+ }
+ }
+
+ public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {}
+ }
+
+ /*
+ * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
+ * at least a second level of protection. First level is that the file
+ * is in a location only readable by the system process.
+ * @param pattern the gesture pattern.
+ * @return the hash of the pattern in a byte array.
+ */
+ public byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+ if (pattern == null) {
+ return null;
+ }
+
+ final int patternSize = pattern.size();
+ byte[] res = new byte[patternSize];
+ for (int i = 0; i < patternSize; i++) {
+ LockPatternView.Cell cell = pattern.get(i);
+ res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
+ }
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] hash = md.digest(res);
+ return hash;
+ } catch (NoSuchAlgorithmException nsa) {
+ return res;
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (mFingerPrintSetUp) {
+ mFingerPrintUiHelper.stopListening();
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mFingerPrintSetUp) {
+ mPatternLockHeader.setText(getString(R.string.pa_pattern_or_fingerprint_header));
+ mFingerPrintUiHelper.startListening();
+ }
+ if (isRetryLocked()) {
+ invalidateOptionsMenu();
+ switchToAccount();
+ }
+ }
+
+ private boolean isRetryLocked() {
+ long time = System.currentTimeMillis();
+ SharedPreferences prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE);
+ long retryTime = prefs.getLong(TIMEOUT_PREF_KEY, 0);
+ mRetryLocked = (time - retryTime) < (FAILED_ATTEMPT_RETRY * 1000);
+ return mRetryLocked;
+ }
+
+ private void setPatternTimeout() {
+ SharedPreferences prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE);
+ prefs.edit().putLong(TIMEOUT_PREF_KEY, System.currentTimeMillis()).apply();
+ }
+}