diff options
Diffstat (limited to 'src/com/android/settings/applications/LockPatternActivity.java')
-rw-r--r-- | src/com/android/settings/applications/LockPatternActivity.java | 466 |
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(); + } +} |