From 0a09d1650052ec8e2298182d0bcf9d58886468c3 Mon Sep 17 00:00:00 2001 From: tarsin Date: Wed, 25 May 2022 15:24:35 +0800 Subject: [PATCH] Drop LockPatternView, migrate to system's provider --- app/build.gradle | 4 +- .../java/com/hippo/ehviewer/Settings.java | 14 +- .../com/hippo/ehviewer/ui/MainActivity.java | 4 +- .../ehviewer/ui/fragment/PrivacyFragment.java | 11 - .../ui/fragment/SetSecurityFragment.java | 87 -- .../hippo/ehviewer/ui/scene/SecurityScene.kt | 212 +--- .../widget/lockpattern/LockPatternUtils.java | 63 - .../widget/lockpattern/LockPatternView.java | 1122 ----------------- .../res/layout-land/activity_set_security.xml | 92 -- .../main/res/layout/activity_set_security.xml | 95 -- app/src/main/res/layout/scene_security.xml | 38 - app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/privacy_settings.xml | 8 +- 14 files changed, 57 insertions(+), 1695 deletions(-) delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/fragment/SetSecurityFragment.java delete mode 100644 app/src/main/java/com/hippo/widget/lockpattern/LockPatternUtils.java delete mode 100644 app/src/main/java/com/hippo/widget/lockpattern/LockPatternView.java delete mode 100755 app/src/main/res/layout-land/activity_set_security.xml delete mode 100755 app/src/main/res/layout/activity_set_security.xml delete mode 100755 app/src/main/res/layout/scene_security.xml diff --git a/app/build.gradle b/app/build.gradle index 41fdd3c5a..1de522caf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -100,8 +100,8 @@ tasks.withType(JavaCompile) { dependencies { implementation 'androidx.activity:activity:1.4.0' - implementation "androidx.activity:activity-compose:1.4.0" - implementation 'androidx.biometric:biometric:1.1.0' + implementation 'androidx.activity:activity-compose:1.4.0' + implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04' implementation 'androidx.browser:browser:1.4.0' implementation 'androidx.core:core:1.7.0' implementation 'androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03' diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index 24b091dbb..819a801f3 100755 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -86,7 +86,7 @@ public class Settings { ****** Advanced ********************/ public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body"; - public static final String KEY_SECURITY = "security"; + public static final String KEY_SECURITY = "require_unlock"; public static final String DEFAULT_SECURITY = ""; public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint"; public static final String KEY_READ_CACHE_SIZE = "read_cache_size"; @@ -1028,22 +1028,14 @@ public class Settings { return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG); } - public static String getSecurity() { - return getString(KEY_SECURITY, DEFAULT_SECURITY); - } - - public static void putSecurity(String value) { - putString(KEY_SECURITY, value); + public static boolean getSecurity() { + return getBoolean(KEY_SECURITY, false); } public static boolean getEnableFingerprint() { return getBoolean(KEY_ENABLE_FINGERPRINT, true); } - public static void putEnableFingerprint(boolean value) { - putBoolean(KEY_ENABLE_FINGERPRINT, value); - } - public static int getReadCacheSize() { return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java index 2f93fe66a..70964ef84 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java @@ -143,7 +143,7 @@ public final class MainActivity extends StageActivity @NonNull @Override protected Announcer getLaunchAnnouncer() { - if (!TextUtils.isEmpty(Settings.getSecurity())) { + if (Settings.getSecurity()) { return new Announcer(SecurityScene.class); } else if (Settings.getShowWarning()) { return new Announcer(WarningScene.class); @@ -161,7 +161,7 @@ public final class MainActivity extends StageActivity // Sometimes scene can't show directly private Announcer processAnnouncer(Announcer announcer) { if (0 == getSceneCount()) { - if (!TextUtils.isEmpty(Settings.getSecurity())) { + if (Settings.getSecurity()) { Bundle newArgs = new Bundle(); newArgs.putString(SecurityScene.KEY_TARGET_SCENE, announcer.getClazz().getName()); newArgs.putBundle(SecurityScene.KEY_TARGET_ARGS, announcer.getArgs()); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/PrivacyFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/PrivacyFragment.java index 381b49520..0055ad3e2 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/fragment/PrivacyFragment.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/PrivacyFragment.java @@ -14,22 +14,11 @@ import com.hippo.ehviewer.Settings; */ public class PrivacyFragment extends BasePreferenceFragment { - private static final String KEY_PATTERN_PROTECTION = "pattern_protection"; - @Override public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.privacy_settings); } - @Override - public void onStart() { - super.onStart(); - Preference patternProtection = findPreference(KEY_PATTERN_PROTECTION); - patternProtection.setSummary(TextUtils.isEmpty(Settings.getSecurity()) ? - R.string.settings_privacy_pattern_protection_not_set : - R.string.settings_privacy_pattern_protection_set); - } - @Override public int getFragmentTitle() { return R.string.settings_privacy; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/SetSecurityFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/SetSecurityFragment.java deleted file mode 100644 index 8beeb740c..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/fragment/SetSecurityFragment.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.hippo.ehviewer.ui.fragment; - -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.biometric.BiometricManager; - -import com.hippo.ehviewer.R; -import com.hippo.ehviewer.Settings; -import com.hippo.widget.lockpattern.LockPatternUtils; -import com.hippo.widget.lockpattern.LockPatternView; -import com.hippo.yorozuya.ViewUtils; - -public class SetSecurityFragment extends BaseFragment implements View.OnClickListener { - - @Nullable - private LockPatternView mPatternView; - @Nullable - private View mCancel; - @Nullable - private View mSet; - @Nullable - private CheckBox mFingerprint; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.activity_set_security, container, false); - - mPatternView = (LockPatternView) ViewUtils.$$(view, R.id.pattern_view); - mCancel = ViewUtils.$$(view, R.id.cancel); - mSet = ViewUtils.$$(view, R.id.set); - mFingerprint = (CheckBox) ViewUtils.$$(view, R.id.fingerprint_checkbox); - - String pattern = Settings.getSecurity(); - if (!TextUtils.isEmpty(pattern)) { - mPatternView.setPattern(LockPatternView.DisplayMode.Correct, - LockPatternUtils.stringToPattern(pattern)); - } - - if (BiometricManager.from(requireContext()).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS) { - mFingerprint.setVisibility(View.VISIBLE); - mFingerprint.setChecked(Settings.getEnableFingerprint()); - } - - mCancel.setOnClickListener(this); - mSet.setOnClickListener(this); - return view; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mPatternView = null; - } - - @Override - public void onClick(View v) { - if (v == mCancel) { - requireActivity().onBackPressed(); - } else if (v == mSet) { - if (null != mPatternView && null != mFingerprint) { - String security; - if (mPatternView.getCellSize() <= 1) { - security = ""; - } else { - security = mPatternView.getPatternString(); - } - Settings.putSecurity(security); - Settings.putEnableFingerprint(mFingerprint.getVisibility() == View.VISIBLE && - mFingerprint.isChecked() && !security.isEmpty()); - } - requireActivity().onBackPressed(); - } - } - - @Override - public int getFragmentTitle() { - return R.string.set_pattern_protection; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/SecurityScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/SecurityScene.kt index 392b3a51a..1448eb59a 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/SecurityScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/SecurityScene.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Hippo Seven + * Copyright 2022 tarsin norbin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,181 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.hippo.ehviewer.ui.scene -package com.hippo.ehviewer.ui.scene; +import android.os.Bundle +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL +import androidx.biometric.BiometricPrompt +import androidx.biometric.auth.AuthPromptCallback +import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import com.hippo.ehviewer.R -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorManager; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.biometric.BiometricManager; -import androidx.biometric.BiometricPrompt; - -import com.hippo.ehviewer.R; -import com.hippo.ehviewer.Settings; -import com.hippo.ehviewer.ui.MainActivity; -import com.hippo.widget.lockpattern.LockPatternUtils; -import com.hippo.widget.lockpattern.LockPatternView; -import com.hippo.yorozuya.ObjectUtils; -import com.hippo.yorozuya.ViewUtils; - -import java.util.List; -import java.util.concurrent.Executors; - -public class SecurityScene extends SolidScene implements - LockPatternView.OnPatternListener { - - private static final int MAX_RETRY_TIMES = 5; - private static final String KEY_RETRY_TIMES = "retry_times"; - - @Nullable - private LockPatternView mPatternView; - - private SensorManager mSensorManager; - private Sensor mAccelerometer; - private BiometricPrompt.PromptInfo promptInfo; - private BiometricPrompt biometricPrompt; - private boolean canAuthenticate = false; - - private int mRetryTimes; - - @Override - public boolean needShowLeftDrawer() { - return false; +class SecurityScene : SolidScene() { + override fun needShowLeftDrawer(): Boolean { + return false } - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Context context = requireContext(); - mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if (null != mSensorManager) { - mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - } - - if (null == savedInstanceState) { - mRetryTimes = MAX_RETRY_TIMES; - } else { - mRetryTimes = savedInstanceState.getInt(KEY_RETRY_TIMES); - } - - canAuthenticate = Settings.getEnableFingerprint() && - BiometricManager.from(requireContext()).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS; - biometricPrompt = new BiometricPrompt(this, Executors.newSingleThreadExecutor(), new BiometricPrompt.AuthenticationCallback() { - @Override - public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { - super.onAuthenticationError(errorCode, errString); - } - - @Override - public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { - startSceneForCheckStep(CHECK_STEP_SECURITY, getArguments()); - finish(); - } - - @Override - public void onAuthenticationFailed() { - super.onAuthenticationFailed(); - } - }); - promptInfo = new BiometricPrompt.PromptInfo.Builder() - .setTitle(getString(R.string.app_name)) - .setNegativeButtonText(getString(android.R.string.cancel)) - .setConfirmationRequired(false) - .build(); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (isAuthenticationSupported()) + startAuthentication(getString(R.string.settings_privacy_require_unlock)) + else + startSceneForCheckStep(CHECK_STEP_SECURITY, arguments) } - @Override - public void onDestroy() { - super.onDestroy(); - - mSensorManager = null; - mAccelerometer = null; + private fun isAuthenticationSupported(): Boolean { + val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL + return BiometricManager.from(requireContext()).canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS } - @Override - public void onResume() { - super.onResume(); + private fun startAuthentication( + title: String, + subtitle: String? = null, + confirmationRequired: Boolean = true, + ) { + startClass2BiometricOrCredentialAuthentication( + title = title, + subtitle = subtitle, + confirmationRequired = confirmationRequired, + executor = ContextCompat.getMainExecutor(requireContext()), + callback = AuthenticationCallback() + ) + } - if (canAuthenticate) { - startBiometricPrompt(); + inner class AuthenticationCallback : AuthPromptCallback() { + override fun onAuthenticationSucceeded( + activity: FragmentActivity?, + result: BiometricPrompt.AuthenticationResult + ) { + super.onAuthenticationSucceeded(activity, result) + startSceneForCheckStep(CHECK_STEP_SECURITY, arguments) } } - - private void startBiometricPrompt() { - biometricPrompt.authenticate(promptInfo); - } - - @Override - public void onPause() { - super.onPause(); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(KEY_RETRY_TIMES, mRetryTimes); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.scene_security, container, false); - if (canAuthenticate) { - view.setOnClickListener(v -> startBiometricPrompt()); - } - - mPatternView = (LockPatternView) ViewUtils.$$(view, R.id.pattern_view); - mPatternView.setOnPatternListener(this); - return view; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - mPatternView = null; - } - - @Override - public void onPatternStart() { - } - - @Override - public void onPatternCleared() { - } - - @Override - public void onPatternCellAdded(List pattern) { - } - - @Override - public void onPatternDetected(List pattern) { - MainActivity activity = getMainActivity(); - if (null == activity || null == mPatternView) { - return; - } - - String enteredPatter = LockPatternUtils.patternToString(pattern); - String targetPatter = Settings.getSecurity(); - - if (ObjectUtils.equal(enteredPatter, targetPatter)) { - startSceneForCheckStep(CHECK_STEP_SECURITY, getArguments()); - finish(); - } else { - mPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); - mRetryTimes--; - if (mRetryTimes <= 0) { - finish(); - } - } - } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/widget/lockpattern/LockPatternUtils.java b/app/src/main/java/com/hippo/widget/lockpattern/LockPatternUtils.java deleted file mode 100644 index ef28ae4f7..000000000 --- a/app/src/main/java/com/hippo/widget/lockpattern/LockPatternUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2007 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.hippo.widget.lockpattern; - -import java.util.ArrayList; -import java.util.List; - -/** - * Utilities for the lock pattern and its settings. - */ -public class LockPatternUtils { - - /** - * Deserialize a pattern. - * - * @param string The pattern serialized with {@link #patternToString} - * @return The pattern. - */ - public static List stringToPattern(String string) { - List result = new ArrayList<>(); - - final byte[] bytes = string.getBytes(); - for (byte b : bytes) { - result.add(LockPatternView.Cell.of(b / 3, b % 3)); - } - return result; - } - - /** - * Serialize a pattern. - * - * @param pattern The pattern. - * @return The pattern in string form. - */ - public static String patternToString(List pattern) { - if (pattern == null) { - return ""; - } - 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()); - } - return new String(res); - } - -} diff --git a/app/src/main/java/com/hippo/widget/lockpattern/LockPatternView.java b/app/src/main/java/com/hippo/widget/lockpattern/LockPatternView.java deleted file mode 100644 index 2b0e66333..000000000 --- a/app/src/main/java/com/hippo/widget/lockpattern/LockPatternView.java +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Copyright (C) 2007 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.hippo.widget.lockpattern; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.os.Debug; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.Interpolator; - -import androidx.annotation.NonNull; -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; -import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; - -import com.hippo.ehviewer.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * Displays and detects the user's unlock attempt, which is a drag of a finger - * across 9 regions of the screen. - *

- * Is also capable of displaying a static pattern in "in progress", "wrong" or - * "correct" states. - */ -@SuppressWarnings("IntegerDivisionInFloatingPointContext") -public class LockPatternView extends View { - // Aspect to use when rendering this view - private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height - private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h) - private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) - private static final boolean PROFILE_DRAWING = false; - /** - * How many milliseconds we spend animating each circle of a lock pattern - * if the animating mode is set. The entire animation should take this - * constant * the length of the pattern to complete. - */ - private static final int MILLIS_PER_CIRCLE_ANIMATING = 700; - /** - * This can be used to avoid updating the display for very small motions or noisy panels. - * It didn't seem to have much impact on the devices tested, so currently set to 0. - */ - private static final float DRAG_THRESHOLD = 0.0f; - private final Cell[][] mCells; - private final CellState[][] mCellStates; - private final int mDotSize; - private final int mDotSizeActivated; - private final int mPathWidth; - private final Paint mPaint = new Paint(); - private final Paint mPathPaint = new Paint(); - private final ArrayList mPattern = new ArrayList<>(9); - /** - * Lookup table for the circles of the pattern we are currently drawing. - * This will be the cells of the complete pattern unless we are animating, - * in which case we use this to hold the cells we are drawing for the in - * progress animation. - */ - private final boolean[][] mPatternDrawLookup = new boolean[3][3]; - private final float mHitFactor = 0.6f; - private final Path mCurrentPath = new Path(); - private final Rect mInvalidate = new Rect(); - private final Rect mTmpInvalidateRect = new Rect(); - private final int mAspect; - private final int mRegularColor; - private final int mErrorColor; - private final int mSuccessColor; - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mLinearOutSlowInInterpolator; - private boolean mDrawingProfilingStarted = false; - private OnPatternListener mOnPatternListener; - /** - * the in progress point: - * - during interaction: where the user's finger is - * - during animation: the current tip of the animating line - */ - private float mInProgressX = -1; - private float mInProgressY = -1; - private long mAnimatingPeriodStart; - private DisplayMode mPatternDisplayMode = DisplayMode.Correct; - private boolean mInputEnabled = true; - private boolean mInStealthMode = false; - private boolean mPatternInProgress = false; - private float mSquareWidth; - private float mSquareHeight; - - public LockPatternView(Context context) { - this(context, null); - } - - public LockPatternView(Context context, AttributeSet attrs) { - super(context, attrs); - - //TypedArray a = context.obtainStyledAttributes(attrs, LockPatternView); - - //final String aspect = a.getString(R.styleable.LockPatternView_aspect); - - //if ("square".equals(aspect)) { - // mAspect = ASPECT_SQUARE; - //} else if ("lock_width".equals(aspect)) { - // mAspect = ASPECT_LOCK_WIDTH; - //} else if ("lock_height".equals(aspect)) { - // mAspect = ASPECT_LOCK_HEIGHT; - //} else { - mAspect = ASPECT_SQUARE; - //} - - setClickable(true); - - - mPathPaint.setAntiAlias(true); - mPathPaint.setDither(true); - - mRegularColor = getResources().getColor(R.color.lock_pattern_view_regular_color); - mErrorColor = getResources().getColor(R.color.lock_pattern_view_error_color); - mSuccessColor = mErrorColor; - - mPathPaint.setColor(mRegularColor); - - mPathPaint.setStyle(Paint.Style.STROKE); - mPathPaint.setStrokeJoin(Paint.Join.ROUND); - mPathPaint.setStrokeCap(Paint.Cap.ROUND); - - mPathWidth = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_line_width); - mPathPaint.setStrokeWidth(mPathWidth); - - mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size); - mDotSizeActivated = getResources().getDimensionPixelSize( - R.dimen.lock_pattern_dot_size_activated); - - mPaint.setAntiAlias(true); - mPaint.setDither(true); - - mCellStates = new CellState[3][3]; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - mCellStates[i][j] = new CellState(); - mCellStates[i][j].radius = mDotSize / 2; - mCellStates[i][j].row = i; - mCellStates[i][j].col = j; - } - } - mCells = new Cell[3][3]; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - mCells[i][j] = Cell.of(i, j); - } - } - - mFastOutSlowInInterpolator = new FastOutSlowInInterpolator(); - mLinearOutSlowInInterpolator = new LinearOutSlowInInterpolator(); - } - - /** - * Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if - * {@code a == 0 && b == 0}. - * - * @throws IllegalArgumentException if {@code a < 0} or {@code b < 0} - */ - private static int gcd(int a, int b) { - - /* - * The reason we require both arguments to be >= 0 is because otherwise, what do you return - * on gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive - * 2^31 isn't an int. - */ - //checkNonNegative("a", a); - if (a < 0) { - throw new IllegalArgumentException("a (" + a + ") must be >= 0"); - } - //checkNonNegative("b", b); - if (b < 0) { - throw new IllegalArgumentException("b (" + b + ") must be >= 0"); - } - - if (a == 0) { - // 0 % b == 0, so b divides a, but the converse doesn't hold. - // BigInteger.gcd is consistent with this decision. - return b; - } else if (b == 0) { - return a; // similar logic - } - /* - * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. - * This is >40% faster than the Euclidean algorithm in benchmarks. - */ - int aTwos = Integer.numberOfTrailingZeros(a); - a >>= aTwos; // divide out all 2s - int bTwos = Integer.numberOfTrailingZeros(b); - b >>= bTwos; // divide out all 2s - while (a != b) { // both a, b are odd - - // The key to the binary GCD algorithm is as follows: - // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). - // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. - - // We bend over backwards to avoid branching, adapting a technique from - // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax - - int delta = a - b; // can't overflow, since a and b are nonnegative - - int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1)); - // equivalent to Math.min(delta, 0) - - a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) - // a is now nonnegative and even - - b += minDeltaOrZero; // sets b to min(old a, b) - a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b - } - return a << Math.min(aTwos, bTwos); - } - - public int getCellSize() { - return mPattern.size(); - } - - public String getPatternString() { - return LockPatternUtils.patternToString(mPattern); - } - - /** - * Set the call back for pattern detection. - * - * @param onPatternListener The call back. - */ - public void setOnPatternListener( - OnPatternListener onPatternListener) { - mOnPatternListener = onPatternListener; - } - - /** - * Set the pattern explicitly (rather than waiting for the user to input - * a pattern). - * - * @param displayMode How to display the pattern. - * @param pattern The pattern. - */ - public void setPattern(DisplayMode displayMode, List pattern) { - mPattern.clear(); - mPattern.addAll(pattern); - clearPatternDrawLookup(); - for (Cell cell : pattern) { - mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true; - } - - setDisplayMode(displayMode); - } - - /** - * Set the display mode of the current pattern. This can be useful, for - * instance, after detecting a pattern to tell this view whether change the - * in progress result to correct or wrong. - * - * @param displayMode The display mode. - */ - public void setDisplayMode(DisplayMode displayMode) { - mPatternDisplayMode = displayMode; - if (displayMode == DisplayMode.Animate) { - if (mPattern.size() == 0) { - throw new IllegalStateException("you must have a pattern to " - + "animate if you want to set the display mode to animate"); - } - mAnimatingPeriodStart = SystemClock.elapsedRealtime(); - final Cell first = mPattern.get(0); - mInProgressX = getCenterXForColumn(first.getColumn()); - mInProgressY = getCenterYForRow(first.getRow()); - clearPatternDrawLookup(); - } - invalidate(); - } - - private void notifyCellAdded() { - if (mOnPatternListener != null) { - mOnPatternListener.onPatternCellAdded(mPattern); - } - } - - private void notifyPatternStarted() { - if (mOnPatternListener != null) { - mOnPatternListener.onPatternStart(); - } - } - - private void notifyPatternDetected() { - if (mOnPatternListener != null) { - mOnPatternListener.onPatternDetected(mPattern); - } - } - - private void notifyPatternCleared() { - if (mOnPatternListener != null) { - mOnPatternListener.onPatternCleared(); - } - } - - /** - * Reset all pattern state. - */ - private void resetPattern() { - mPattern.clear(); - clearPatternDrawLookup(); - mPatternDisplayMode = DisplayMode.Correct; - invalidate(); - } - - /** - * Clear the pattern lookup table. - */ - private void clearPatternDrawLookup() { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - mPatternDrawLookup[i][j] = false; - } - } - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - final int width = w - getPaddingLeft() - getPaddingRight(); - mSquareWidth = width / 3.0f; - - final int height = h - getPaddingTop() - getPaddingBottom(); - mSquareHeight = height / 3.0f; - } - - private int resolveMeasured(int measureSpec, int desired) { - int result; - int specSize = MeasureSpec.getSize(measureSpec); - switch (MeasureSpec.getMode(measureSpec)) { - case MeasureSpec.UNSPECIFIED: - result = desired; - break; - case MeasureSpec.AT_MOST: - result = Math.max(specSize, desired); - break; - case MeasureSpec.EXACTLY: - default: - result = specSize; - } - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int minimumWidth = getSuggestedMinimumWidth(); - final int minimumHeight = getSuggestedMinimumHeight(); - int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth); - int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight); - - switch (mAspect) { - case ASPECT_SQUARE: - viewWidth = viewHeight = Math.min(viewWidth, viewHeight); - break; - case ASPECT_LOCK_WIDTH: - viewHeight = Math.min(viewWidth, viewHeight); - break; - case ASPECT_LOCK_HEIGHT: - viewWidth = Math.min(viewWidth, viewHeight); - break; - } - // Log.v(TAG, "LockPatternView dimensions: " + viewWidth + "x" + viewHeight); - setMeasuredDimension(viewWidth, viewHeight); - } - - // From - // https://github.com/google/guava/blob/c462d69329709f72a17a64cb229d15e76e72199c/guava/src/com/google/common/math/IntMath.java - - /** - * Determines whether the point x, y will add a new point to the current - * pattern (in addition to finding the cell, also makes heuristic choices - * such as filling in gaps based on current pattern). - * - * @param x The x coordinate. - * @param y The y coordinate. - */ - private Cell detectAndAddHit(float x, float y) { - final Cell cell = checkForNewHit(x, y); - if (cell != null) { - - // check for gaps in existing pattern - final ArrayList pattern = mPattern; - if (!pattern.isEmpty()) { - final Cell lastCell = pattern.get(pattern.size() - 1); - int dRow = cell.row - lastCell.row; - int dColumn = cell.column - lastCell.column; - int dGcd = gcd(Math.abs(dRow), Math.abs(dColumn)); - if (dGcd > 0) { - int fillInRow = lastCell.row; - int fillInColumn = lastCell.column; - int fillInRowStep = dRow / dGcd; - int fillInColumnStep = dColumn / dGcd; - for (int i = 1; i < dGcd; ++i) { - fillInRow += fillInRowStep; - fillInColumn += fillInColumnStep; - if (!mPatternDrawLookup[fillInRow][fillInColumn]) { - addCellToPattern(mCells[fillInRow][fillInColumn]); - } - } - } - } - addCellToPattern(cell); - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - - return cell; - } - return null; - } - - private void addCellToPattern(Cell newCell) { - mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; - mPattern.add(newCell); - if (!mInStealthMode) { - startCellActivatedAnimation(newCell); - } - notifyCellAdded(); - } - - private void startCellActivatedAnimation(Cell cell) { - final CellState cellState = mCellStates[cell.row][cell.column]; - startRadiusAnimation(mDotSize / 2, mDotSizeActivated / 2, 96, mLinearOutSlowInInterpolator, - cellState, () -> startRadiusAnimation(mDotSizeActivated / 2, mDotSize / 2, 192, - mFastOutSlowInInterpolator, - cellState, null)); - startLineEndAnimation(cellState, mInProgressX, mInProgressY, - getCenterXForColumn(cell.column), getCenterYForRow(cell.row)); - } - - private void startLineEndAnimation(final CellState state, - final float startX, final float startY, final float targetX, final float targetY) { - ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.addUpdateListener(animation -> { - float t = (float) animation.getAnimatedValue(); - state.lineEndX = (1 - t) * startX + t * targetX; - state.lineEndY = (1 - t) * startY + t * targetY; - invalidate(); - }); - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - state.lineAnimator = null; - } - }); - valueAnimator.setInterpolator(mFastOutSlowInInterpolator); - valueAnimator.setDuration(100); - valueAnimator.start(); - state.lineAnimator = valueAnimator; - } - - private void startRadiusAnimation(float start, float end, long duration, - Interpolator interpolator, final CellState state, - final Runnable endRunnable) { - ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end); - valueAnimator.addUpdateListener(animation -> { - state.radius = (float) animation.getAnimatedValue(); - invalidate(); - }); - if (endRunnable != null) { - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - endRunnable.run(); - } - }); - } - valueAnimator.setInterpolator(interpolator); - valueAnimator.setDuration(duration); - valueAnimator.start(); - } - - // helper method to find which cell a point maps to - private Cell checkForNewHit(float x, float y) { - - final int rowHit = getRowHit(y); - if (rowHit < 0) { - return null; - } - final int columnHit = getColumnHit(x); - if (columnHit < 0) { - return null; - } - - if (mPatternDrawLookup[rowHit][columnHit]) { - return null; - } - return mCells[rowHit][columnHit]; - } - - /** - * Helper method to find the row that y falls into. - * - * @param y The y coordinate - * @return The row that y falls in, or -1 if it falls in no row. - */ - private int getRowHit(float y) { - - final float squareHeight = mSquareHeight; - float hitSize = squareHeight * mHitFactor; - - float offset = getPaddingTop() + (squareHeight - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitTop = offset + squareHeight * i; - if (y >= hitTop && y <= hitTop + hitSize) { - return i; - } - } - return -1; - } - - /** - * Helper method to find the column x fallis into. - * - * @param x The x coordinate. - * @return The column that x falls in, or -1 if it falls in no column. - */ - private int getColumnHit(float x) { - final float squareWidth = mSquareWidth; - float hitSize = squareWidth * mHitFactor; - - float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitLeft = offset + squareWidth * i; - if (x >= hitLeft && x <= hitLeft + hitSize) { - return i; - } - } - return -1; - } - - @Override - public boolean onHoverEvent(@NonNull MotionEvent event) { - AccessibilityManager accessibilityManager = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isTouchExplorationEnabled()) { - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - event.setAction(MotionEvent.ACTION_DOWN); - break; - case MotionEvent.ACTION_HOVER_MOVE: - event.setAction(MotionEvent.ACTION_MOVE); - break; - case MotionEvent.ACTION_HOVER_EXIT: - event.setAction(MotionEvent.ACTION_UP); - break; - } - onTouchEvent(event); - event.setAction(action); - } - return super.onHoverEvent(event); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(@NonNull MotionEvent event) { - if (!mInputEnabled || !isEnabled()) { - return false; - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - handleActionDown(event); - return true; - case MotionEvent.ACTION_UP: - handleActionUp(); - return true; - case MotionEvent.ACTION_MOVE: - handleActionMove(event); - return true; - case MotionEvent.ACTION_CANCEL: - if (mPatternInProgress) { - setPatternInProgress(false); - resetPattern(); - notifyPatternCleared(); - } - if (PROFILE_DRAWING) { - if (mDrawingProfilingStarted) { - Debug.stopMethodTracing(); - mDrawingProfilingStarted = false; - } - } - return true; - } - return false; - } - - private void setPatternInProgress(boolean progress) { - mPatternInProgress = progress; - } - - private void handleActionMove(MotionEvent event) { - // Handle all recent motion events so we don't skip any cells even when the device - // is busy... - final float radius = mPathWidth; - final int historySize = event.getHistorySize(); - mTmpInvalidateRect.setEmpty(); - boolean invalidateNow = false; - for (int i = 0; i < historySize + 1; i++) { - final float x = i < historySize ? event.getHistoricalX(i) : event.getX(); - final float y = i < historySize ? event.getHistoricalY(i) : event.getY(); - Cell hitCell = detectAndAddHit(x, y); - final int patternSize = mPattern.size(); - if (hitCell != null && patternSize == 1) { - setPatternInProgress(true); - notifyPatternStarted(); - } - // note current x and y for rubber banding of in progress patterns - final float dx = Math.abs(x - mInProgressX); - final float dy = Math.abs(y - mInProgressY); - if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) { - invalidateNow = true; - } - - if (mPatternInProgress && patternSize > 0) { - final Cell lastCell = mPattern.get(patternSize - 1); - float lastCellCenterX = getCenterXForColumn(lastCell.column); - float lastCellCenterY = getCenterYForRow(lastCell.row); - - // Adjust for drawn segment from last cell to (x,y). Radius accounts for line width. - float left = Math.min(lastCellCenterX, x) - radius; - float right = Math.max(lastCellCenterX, x) + radius; - float top = Math.min(lastCellCenterY, y) - radius; - float bottom = Math.max(lastCellCenterY, y) + radius; - - // Invalidate between the pattern's new cell and the pattern's previous cell - if (hitCell != null) { - final float width = mSquareWidth * 0.5f; - final float height = mSquareHeight * 0.5f; - final float hitCellCenterX = getCenterXForColumn(hitCell.column); - final float hitCellCenterY = getCenterYForRow(hitCell.row); - - left = Math.min(hitCellCenterX - width, left); - right = Math.max(hitCellCenterX + width, right); - top = Math.min(hitCellCenterY - height, top); - bottom = Math.max(hitCellCenterY + height, bottom); - } - - // Invalidate between the pattern's last cell and the previous location - mTmpInvalidateRect.union(Math.round(left), Math.round(top), - Math.round(right), Math.round(bottom)); - } - } - mInProgressX = event.getX(); - mInProgressY = event.getY(); - - // To save updates, we only invalidate if the user moved beyond a certain amount. - if (invalidateNow) { - mInvalidate.union(mTmpInvalidateRect); - invalidate(mInvalidate); - mInvalidate.set(mTmpInvalidateRect); - } - } - - private void handleActionUp() { - // report pattern detected - if (!mPattern.isEmpty()) { - setPatternInProgress(false); - cancelLineAnimations(); - notifyPatternDetected(); - invalidate(); - } - if (PROFILE_DRAWING) { - if (mDrawingProfilingStarted) { - Debug.stopMethodTracing(); - mDrawingProfilingStarted = false; - } - } - } - - private void cancelLineAnimations() { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - CellState state = mCellStates[i][j]; - if (state.lineAnimator != null) { - state.lineAnimator.cancel(); - state.lineEndX = Float.MIN_VALUE; - state.lineEndY = Float.MIN_VALUE; - } - } - } - } - - private void handleActionDown(MotionEvent event) { - resetPattern(); - final float x = event.getX(); - final float y = event.getY(); - final Cell hitCell = detectAndAddHit(x, y); - if (hitCell != null) { - setPatternInProgress(true); - mPatternDisplayMode = DisplayMode.Correct; - notifyPatternStarted(); - } else if (mPatternInProgress) { - setPatternInProgress(false); - notifyPatternCleared(); - } - if (hitCell != null) { - final float startX = getCenterXForColumn(hitCell.column); - final float startY = getCenterYForRow(hitCell.row); - - final float widthOffset = mSquareWidth / 2f; - final float heightOffset = mSquareHeight / 2f; - - invalidate((int) (startX - widthOffset), (int) (startY - heightOffset), - (int) (startX + widthOffset), (int) (startY + heightOffset)); - } - mInProgressX = x; - mInProgressY = y; - if (PROFILE_DRAWING) { - if (!mDrawingProfilingStarted) { - Debug.startMethodTracing("LockPatternDrawing"); - mDrawingProfilingStarted = true; - } - } - } - - private float getCenterXForColumn(int column) { - return getPaddingLeft() + column * mSquareWidth + mSquareWidth / 2f; - } - - private float getCenterYForRow(int row) { - return getPaddingTop() + row * mSquareHeight + mSquareHeight / 2f; - } - - @Override - protected void onDraw(Canvas canvas) { - final ArrayList pattern = mPattern; - final int count = pattern.size(); - final boolean[][] drawLookup = mPatternDrawLookup; - - if (mPatternDisplayMode == DisplayMode.Animate) { - - // figure out which circles to draw - - // + 1 so we pause on complete pattern - final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING; - final int spotInCycle = (int) (SystemClock.elapsedRealtime() - - mAnimatingPeriodStart) % oneCycle; - final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING; - - clearPatternDrawLookup(); - for (int i = 0; i < numCircles; i++) { - final Cell cell = pattern.get(i); - drawLookup[cell.getRow()][cell.getColumn()] = true; - } - - // figure out in progress portion of ghosting line - - final boolean needToUpdateInProgressPoint = numCircles > 0 - && numCircles < count; - - if (needToUpdateInProgressPoint) { - final float percentageOfNextCircle = - ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) / - MILLIS_PER_CIRCLE_ANIMATING; - - final Cell currentCell = pattern.get(numCircles - 1); - final float centerX = getCenterXForColumn(currentCell.column); - final float centerY = getCenterYForRow(currentCell.row); - - final Cell nextCell = pattern.get(numCircles); - final float dx = percentageOfNextCircle * - (getCenterXForColumn(nextCell.column) - centerX); - final float dy = percentageOfNextCircle * - (getCenterYForRow(nextCell.row) - centerY); - mInProgressX = centerX + dx; - mInProgressY = centerY + dy; - } - // TODO: Infinite loop here... - invalidate(); - } - - final Path currentPath = mCurrentPath; - currentPath.rewind(); - - // draw the circles - for (int i = 0; i < 3; i++) { - float centerY = getCenterYForRow(i); - for (int j = 0; j < 3; j++) { - CellState cellState = mCellStates[i][j]; - float centerX = getCenterXForColumn(j); - float translationY = cellState.translationY; - drawCircle(canvas, (int) centerX, (int) centerY + translationY, cellState.radius, - drawLookup[i][j], cellState.alpha); - } - } - - // TODO: the path should be created and cached every time we hit-detect a cell - // only the last segment of the path should be computed here - // draw the path of the pattern (unless we are in stealth mode) - final boolean drawPath = !mInStealthMode; - - if (drawPath) { - mPathPaint.setColor(getCurrentColor(true /* partOfPattern */)); - // Anyway other drawing sets their own alpha ignoring the original; And in this way we - // can use ?colorControlNormal better. - mPathPaint.setAlpha(255); - - boolean anyCircles = false; - float lastX = 0f; - float lastY = 0f; - for (int i = 0; i < count; i++) { - Cell cell = pattern.get(i); - - // only draw the part of the pattern stored in - // the lookup table (this is only different in the case - // of animation). - if (!drawLookup[cell.row][cell.column]) { - break; - } - anyCircles = true; - - float centerX = getCenterXForColumn(cell.column); - float centerY = getCenterYForRow(cell.row); - if (i != 0) { - CellState state = mCellStates[cell.row][cell.column]; - currentPath.rewind(); - currentPath.moveTo(lastX, lastY); - if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) { - currentPath.lineTo(state.lineEndX, state.lineEndY); - } else { - currentPath.lineTo(centerX, centerY); - } - canvas.drawPath(currentPath, mPathPaint); - } - lastX = centerX; - lastY = centerY; - } - - // draw last in progress section - if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate) - && anyCircles) { - currentPath.rewind(); - currentPath.moveTo(lastX, lastY); - currentPath.lineTo(mInProgressX, mInProgressY); - - mPathPaint.setAlpha((int) (calculateLastSegmentAlpha( - mInProgressX, mInProgressY, lastX, lastY) * 255f)); - canvas.drawPath(currentPath, mPathPaint); - } - } - } - - private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) { - float diffX = x - lastX; - float diffY = y - lastY; - float dist = (float) Math.sqrt(diffX * diffX + diffY * diffY); - float frac = dist / mSquareWidth; - return Math.min(1f, Math.max(0f, (frac - 0.3f) * 4f)); - } - - private int getCurrentColor(boolean partOfPattern) { - if (!partOfPattern || mInStealthMode || mPatternInProgress) { - // unselected circle - return mRegularColor; - } else if (mPatternDisplayMode == DisplayMode.Wrong) { - // the pattern is wrong - return mErrorColor; - } else if (mPatternDisplayMode == DisplayMode.Correct || - mPatternDisplayMode == DisplayMode.Animate) { - return mSuccessColor; - } else { - throw new IllegalStateException("unknown display mode " + mPatternDisplayMode); - } - } - - /** - * @param partOfPattern Whether this circle is part of the pattern. - */ - private void drawCircle(Canvas canvas, float centerX, float centerY, float radius, - boolean partOfPattern, float alpha) { - mPaint.setColor(getCurrentColor(partOfPattern)); - mPaint.setAlpha((int) (alpha * 255)); - canvas.drawCircle(centerX, centerY, radius, mPaint); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, - LockPatternUtils.patternToString(mPattern), - mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - final SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setPattern( - DisplayMode.Correct, - LockPatternUtils.stringToPattern(ss.getSerializedPattern())); - mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; - mInputEnabled = ss.isInputEnabled(); - mInStealthMode = ss.isInStealthMode(); - } - - /** - * How to display the current pattern. - */ - public enum DisplayMode { - - /** - * The pattern drawn is correct (i.e draw it in a friendly color) - */ - Correct, - - /** - * Animate the pattern (for demo, and help). - */ - Animate, - - /** - * The pattern is wrong (i.e draw a foreboding color) - */ - Wrong - } - - /** - * The call back interface for detecting patterns entered by the user. - */ - public interface OnPatternListener { - - /** - * A new pattern has begun. - */ - void onPatternStart(); - - /** - * The pattern was cleared. - */ - void onPatternCleared(); - - /** - * The user extended the pattern currently being drawn by one cell. - * - * @param pattern The pattern with newly added cell. - */ - void onPatternCellAdded(List pattern); - - /** - * A pattern was detected from the user. - * - * @param pattern The pattern. - */ - void onPatternDetected(List pattern); - } - - /** - * Represents a cell in the 3 X 3 matrix of the unlock pattern view. - */ - public static class Cell { - // keep # objects limited to 9 - static Cell[][] sCells = new Cell[3][3]; - - static { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - sCells[i][j] = new Cell(i, j); - } - } - } - - int row; - int column; - - /** - * @param row The row of the cell. - * @param column The column of the cell. - */ - private Cell(int row, int column) { - checkRange(row, column); - this.row = row; - this.column = column; - } - - /** - * @param row The row of the cell. - * @param column The column of the cell. - */ - public static synchronized Cell of(int row, int column) { - checkRange(row, column); - return sCells[row][column]; - } - - private static void checkRange(int row, int column) { - if (row < 0 || row > 2) { - throw new IllegalArgumentException("row must be in range 0-2"); - } - if (column < 0 || column > 2) { - throw new IllegalArgumentException("column must be in range 0-2"); - } - } - - public int getRow() { - return row; - } - - public int getColumn() { - return column; - } - - @NonNull - public String toString() { - return "(row=" + row + ",clmn=" + column + ")"; - } - } - - public static class CellState { - public float lineEndX = Float.MIN_VALUE; - public float lineEndY = Float.MIN_VALUE; - public ValueAnimator lineAnimator; - int row; - int col; - float radius; - float translationY; - float alpha = 1f; - } - - /** - * The parecelable for saving and restoring a lock pattern view. - */ - private static class SavedState extends BaseSavedState { - - public static final Creator CREATOR = - new Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - private final String mSerializedPattern; - private final int mDisplayMode; - private final boolean mInputEnabled; - private final boolean mInStealthMode; - - /** - * Constructor called from {@link LockPatternView#onSaveInstanceState()} - */ - private SavedState(Parcelable superState, String serializedPattern, int displayMode, - boolean inputEnabled, boolean inStealthMode) { - super(superState); - mSerializedPattern = serializedPattern; - mDisplayMode = displayMode; - mInputEnabled = inputEnabled; - mInStealthMode = inStealthMode; - } - - /** - * Constructor called from {@link #CREATOR} - */ - @SuppressLint("ParcelClassLoader") - private SavedState(Parcel in) { - super(in); - mSerializedPattern = in.readString(); - mDisplayMode = in.readInt(); - mInputEnabled = (Boolean) in.readValue(null); - mInStealthMode = (Boolean) in.readValue(null); - } - - public String getSerializedPattern() { - return mSerializedPattern; - } - - public int getDisplayMode() { - return mDisplayMode; - } - - public boolean isInputEnabled() { - return mInputEnabled; - } - - public boolean isInStealthMode() { - return mInStealthMode; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeString(mSerializedPattern); - dest.writeInt(mDisplayMode); - dest.writeValue(mInputEnabled); - dest.writeValue(mInStealthMode); - } - } -} \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_set_security.xml b/app/src/main/res/layout-land/activity_set_security.xml deleted file mode 100755 index 0ca7ecff9..000000000 --- a/app/src/main/res/layout-land/activity_set_security.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - -