diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/Common.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/Common.java index e20e5679..b1ce69b4 100644 --- a/app/src/main/java/de/Maxr1998/xposed/maxlock/Common.java +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/Common.java @@ -37,6 +37,8 @@ public abstract class Common { public static final String MASTER_SWITCH_ON = "master_switch_on"; public static final String KEY_PREFERENCE = "key"; public static final String APP_KEY_PREFERENCE = "_key"; + public static final String APP_KEY_GESTURE = "_gesture"; + public static final String MASTER_GESTURE_NAME = "master"; public static final String THEME_PKG = "theme_pkg"; public static final String LOCKING_TYPE_SETTINGS = "locking_type_settings"; @@ -46,6 +48,7 @@ public abstract class Common { public static final String LOCKING_TYPE_PIN = "locking_type_pin"; public static final String LOCKING_TYPE_KNOCK_CODE = "locking_type_knock_code"; public static final String LOCKING_TYPE_PATTERN = "locking_type_pattern"; + public static final String LOCKING_TYPE_GESTURE = "locking_type_gesture"; public static final String LOCKING_UI_SETTINGS = "locking_ui_settings"; /* UI */ @@ -66,6 +69,7 @@ public abstract class Common { public static final String SHADOW_FINGERPRINT = "shadow_fingerprint"; public static final String CATEGORY_FINGERPRINT = "cat_fingerprint"; public static final String DISABLE_FINGERPRINT = "disable_fingerprint"; + public static final String SHOW_GESTURE = "show_gesture"; public static final String LOCKING_OPTIONS = "locking_options"; @@ -109,6 +113,7 @@ public abstract class Common { public static final String PREF_VALUE_PIN = "pin"; public static final String PREF_VALUE_KNOCK_CODE = "knock_code"; public static final String PREF_VALUE_PATTERN = "pattern"; + public static final String PREF_VALUE_GESTURE = "gesture"; // Preference files // public static final String PREFS_KEY = "keys"; @@ -125,6 +130,8 @@ public abstract class Common { public static final String EXTERNAL_FILES_DIR = Environment.getExternalStorageDirectory() + "/MaxLock/"; public static final String BACKUP_DIR = EXTERNAL_FILES_DIR + "Backup/"; + public static final String GESTURES_FILE = "/maxlock_gestures"; + // URLS // public static final Uri WEBSITE_URI = Uri.parse("http://maxlock.maxr1998.de/?client=inapp&lang=" + Util.getLanguageCode()); public static final Uri MAXR1998_URI = Uri.parse("http://maxr1998.de/"); diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureActivityView.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureActivityView.java new file mode 100644 index 00000000..dc8077a1 --- /dev/null +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureActivityView.java @@ -0,0 +1,73 @@ +package de.Maxr1998.xposed.maxlock.ui.lockscreen; + +import android.content.Context; +import android.content.res.Configuration; +import android.gesture.Gesture; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.Prediction; +import android.graphics.Color; +import android.view.MotionEvent; +import android.view.ViewGroup; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; + +import de.Maxr1998.xposed.maxlock.Common; +import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.GestureSetupFragment; + +/** + * Created by rijul on 8/3/16. + */ +public class GestureActivityView extends LockGestureView implements LockGestureView.OnLockGestureListener { + private LockView mLockView; + private final Runnable mClearRunnable = new Runnable() { + @Override + public void run() { + clearGesture(); + setGestureColor(READY_COLOR); + } + }; + + public GestureActivityView(Context context, LockView lockView) { + super(context); + mLockView = lockView; + setOnGestureListener(this); + switch (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) { + case Configuration.SCREENLAYOUT_SIZE_LARGE: + case Configuration.SCREENLAYOUT_SIZE_XLARGE: { + final int size = mLockView.getDimens(com.haibison.android.lockpattern.R.dimen.alp_42447968_lockpatternview_size); + ViewGroup.LayoutParams lp = getLayoutParams(); + lp.width = size; + lp.height = size; + break; + } + } + setInStealthMode(!mLockView.getPrefs().getBoolean(Common.SHOW_GESTURE, true)); + } + + @Override + public void onGestureStart() { + + } + + @Override + public void onGestureCleared() { + + } + + @Override + public void onGestureDetected(Gesture gesture) { + if (gesture.getLength()< GestureSetupFragment.LENGTH_THRESHOLD) { + clearGesture(); + } else { + if (mLockView.checkInput(gesture)) { + setDisplayMode(LockGestureView.DisplayMode.Correct); + } else { + setDisplayMode(LockGestureView.DisplayMode.Wrong); + postDelayed(mClearRunnable, 300); + } + } + } +} diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureOverlayView.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureOverlayView.java new file mode 100644 index 00000000..897516b3 --- /dev/null +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/GestureOverlayView.java @@ -0,0 +1,818 @@ +/* + * Copyright (C) 2009 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 de.Maxr1998.xposed.maxlock.ui.lockscreen; + +import android.content.res.Resources; +import android.gesture.GestureStroke; +import android.gesture.GestureUtils; +import android.content.Context; +import android.content.res.TypedArray; +import android.gesture.Gesture; +import android.gesture.GesturePoint; +import android.gesture.OrientedBoundingBox; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.animation.AnimationUtils; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.FrameLayout; +import android.os.SystemClock; +import de.Maxr1998.xposed.maxlock.R; + +import java.util.ArrayList; + +/** + * A transparent overlay for gesture input that can be placed on top of other + * widgets or contain other widgets. + * + * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled + * @attr ref android.R.styleable#GestureOverlayView_fadeDuration + * @attr ref android.R.styleable#GestureOverlayView_fadeOffset + * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType + * @attr ref android.R.styleable#GestureOverlayView_gestureColor + * @attr ref android.R.styleable#GestureOverlayView_orientation + * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor + */ + +public class GestureOverlayView extends FrameLayout { + static final float TOUCH_TOLERANCE = 3; + public static final int GESTURE_STROKE_TYPE_SINGLE = 0; + public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1; + + public static final int ORIENTATION_HORIZONTAL = 0; + public static final int ORIENTATION_VERTICAL = 1; + + private static final int FADE_ANIMATION_RATE = 16; + private static final boolean GESTURE_RENDERING_ANTIALIAS = true; + private static final boolean DITHER_FLAG = true; + + private final Paint mGesturePaint = new Paint(); + + private long mFadeDuration = 150; + private long mFadeOffset = 420; + private long mFadingStart; + private boolean mFadingHasStarted; + private boolean mFadeEnabled = true; + + private int mCurrentColor; + private int mCertainGestureColor = 0xFFFFFF00; + private int mUncertainGestureColor = 0x48FFFF00; + private float mGestureStrokeWidth = 12.0f; + private int mInvalidateExtraBorder = 10; + + private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE; + private float mGestureStrokeLengthThreshold = 50.0f; + private float mGestureStrokeSquarenessTreshold = 0.275f; + private float mGestureStrokeAngleThreshold = 40.0f; + + private int mOrientation = ORIENTATION_VERTICAL; + + private final Rect mInvalidRect = new Rect(); + private final Path mPath = new Path(); + private boolean mGestureVisible = true; + protected boolean mClearPerformedGesture = true; + protected boolean mInputEnabled = true; + + private float mX; + private float mY; + + private float mCurveEndX; + private float mCurveEndY; + + private float mTotalLength; + private boolean mIsGesturing = false; + private boolean mPreviousWasGesturing = false; + private boolean mInterceptEvents = true; + private boolean mIsListeningForGestures; + private boolean mResetGesture; + + // current gesture + private Gesture mCurrentGesture; + private final ArrayList mStrokeBuffer = new ArrayList(100); + + // TODO: Make this a list of WeakReferences + private final ArrayList mOnGestureListeners = + new ArrayList(); + // TODO: Make this a list of WeakReferences + private final ArrayList mOnGesturePerformedListeners = + new ArrayList(); + // TODO: Make this a list of WeakReferences + private final ArrayList mOnGesturingListeners = + new ArrayList(); + + private boolean mHandleGestureActions; + + // fading out effect + private boolean mIsFadingOut = false; + private float mFadingAlpha = 1.0f; + private final AccelerateDecelerateInterpolator mInterpolator = + new AccelerateDecelerateInterpolator(); + + private final FadeOutRunnable mFadingOut = new FadeOutRunnable(); + + public GestureOverlayView(Context context) { + super(context); + init(); + } + + public GestureOverlayView(Context context, AttributeSet attrs) { + this(context, attrs, Resources.getSystem().getIdentifier("gestureOverlayViewStyle", "attr", "android")); + } + + public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GestureOverlayView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes); + + mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth, + mGestureStrokeWidth); + mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1); + mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor, + mCertainGestureColor); + mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor, + mUncertainGestureColor); + mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration); + mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset); + mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType, + mGestureStrokeType); + mGestureStrokeLengthThreshold = a.getFloat( + R.styleable.GestureOverlayView_gestureStrokeLengthThreshold, + mGestureStrokeLengthThreshold); + mGestureStrokeAngleThreshold = a.getFloat( + R.styleable.GestureOverlayView_gestureStrokeAngleThreshold, + mGestureStrokeAngleThreshold); + mGestureStrokeSquarenessTreshold = a.getFloat( + R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold, + mGestureStrokeSquarenessTreshold); + mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled, + mInterceptEvents); + mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled, + mFadeEnabled); + mOrientation = a.getInt(R.styleable.GestureOverlayView_gestureOrientation, mOrientation); + + a.recycle(); + + init(); + } + + private void init() { + setWillNotDraw(false); + + final Paint gesturePaint = mGesturePaint; + gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS); + gesturePaint.setColor(mCertainGestureColor); + gesturePaint.setStyle(Paint.Style.STROKE); + gesturePaint.setStrokeJoin(Paint.Join.ROUND); + gesturePaint.setStrokeCap(Paint.Cap.ROUND); + gesturePaint.setStrokeWidth(mGestureStrokeWidth); + gesturePaint.setDither(DITHER_FLAG); + + mCurrentColor = mCertainGestureColor; + setPaintAlpha(255); + } + + public ArrayList getCurrentStroke() { + return mStrokeBuffer; + } + + public int getOrientation() { + return mOrientation; + } + + public void setOrientation(int orientation) { + mOrientation = orientation; + } + + public void setGestureColor(int color) { + mCertainGestureColor = color; + setCurrentColor(color); + } + + public void setUncertainGestureColor(int color) { + mUncertainGestureColor = color; + } + + public int getUncertainGestureColor() { + return mUncertainGestureColor; + } + + public int getGestureColor() { + return mCertainGestureColor; + } + + public float getGestureStrokeWidth() { + return mGestureStrokeWidth; + } + + public void setGestureStrokeWidth(float gestureStrokeWidth) { + mGestureStrokeWidth = gestureStrokeWidth; + mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1); + mGesturePaint.setStrokeWidth(gestureStrokeWidth); + } + + public int getGestureStrokeType() { + return mGestureStrokeType; + } + + public void setGestureStrokeType(int gestureStrokeType) { + mGestureStrokeType = gestureStrokeType; + } + + public float getGestureStrokeLengthThreshold() { + return mGestureStrokeLengthThreshold; + } + + public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) { + mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold; + } + + public float getGestureStrokeSquarenessTreshold() { + return mGestureStrokeSquarenessTreshold; + } + + public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) { + mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold; + } + + public float getGestureStrokeAngleThreshold() { + return mGestureStrokeAngleThreshold; + } + + public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) { + mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold; + } + + public boolean isEventsInterceptionEnabled() { + return mInterceptEvents; + } + + public void setEventsInterceptionEnabled(boolean enabled) { + mInterceptEvents = enabled; + } + + public boolean isFadeEnabled() { + return mFadeEnabled; + } + + public void setFadeEnabled(boolean fadeEnabled) { + mFadeEnabled = fadeEnabled; + } + + public Gesture getGesture() { + return mCurrentGesture; + } + + public void setGesture(Gesture gesture) { + if (mCurrentGesture != null) { + clear(false); + } + + setCurrentColor(mCertainGestureColor); + mCurrentGesture = gesture; + + final Path path = mCurrentGesture.toPath(); + final RectF bounds = new RectF(); + path.computeBounds(bounds, true); + + // TODO: The path should also be scaled to fit inside this view + mPath.rewind(); + mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f, + -bounds.top + (getHeight() - bounds.height()) / 2.0f); + + mResetGesture = true; + + invalidate(); + } + + public Path getGesturePath() { + return mPath; + } + + public Path getGesturePath(Path path) { + path.set(mPath); + return path; + } + + public boolean isGestureVisible() { + return mGestureVisible; + } + + public void setGestureVisible(boolean visible) { + mGestureVisible = visible; + } + + public long getFadeOffset() { + return mFadeOffset; + } + + public void setFadeOffset(long fadeOffset) { + mFadeOffset = fadeOffset; + } + + public void addOnGestureListener(OnGestureListener listener) { + mOnGestureListeners.add(listener); + } + + public void removeOnGestureListener(OnGestureListener listener) { + mOnGestureListeners.remove(listener); + } + + public void removeAllOnGestureListeners() { + mOnGestureListeners.clear(); + } + + public void addOnGesturePerformedListener(OnGesturePerformedListener listener) { + mOnGesturePerformedListeners.add(listener); + if (mOnGesturePerformedListeners.size() > 0) { + mHandleGestureActions = true; + } + } + + public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) { + mOnGesturePerformedListeners.remove(listener); + if (mOnGesturePerformedListeners.size() <= 0) { + mHandleGestureActions = false; + } + } + + public void removeAllOnGesturePerformedListeners() { + mOnGesturePerformedListeners.clear(); + mHandleGestureActions = false; + } + + public void addOnGesturingListener(OnGesturingListener listener) { + mOnGesturingListeners.add(listener); + } + + public void removeOnGesturingListener(OnGesturingListener listener) { + mOnGesturingListeners.remove(listener); + } + + public void removeAllOnGesturingListeners() { + mOnGesturingListeners.clear(); + } + + public boolean isGesturing() { + return mIsGesturing; + } + + private void setCurrentColor(int color) { + mCurrentColor = color; + if (mFadingHasStarted) { + setPaintAlpha((int) (255 * mFadingAlpha)); + } else { + setPaintAlpha(255); + } + invalidate(); + } + + /** + * @hide + */ + public Paint getGesturePaint() { + return mGesturePaint; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + if (mCurrentGesture != null && mGestureVisible) { + canvas.drawPath(mPath, mGesturePaint); + } + } + + private void setPaintAlpha(int alpha) { + alpha += alpha >> 7; + final int baseAlpha = mCurrentColor >>> 24; + final int useAlpha = baseAlpha * alpha >> 8; + mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24)); + } + + public void clear(boolean animated) { + clear(animated, false, true); + } + + private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) { + setPaintAlpha(255); + removeCallbacks(mFadingOut); + mResetGesture = false; + mFadingOut.fireActionPerformed = fireActionPerformed; + mFadingOut.resetMultipleStrokes = false; + + if (animated && mCurrentGesture != null) { + mFadingAlpha = 1.0f; + mIsFadingOut = true; + mFadingHasStarted = false; + mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset; + + postDelayed(mFadingOut, mFadeOffset); + } else { + mFadingAlpha = 1.0f; + mIsFadingOut = false; + mFadingHasStarted = false; + + if (immediate) { + mCurrentGesture = null; + mPath.rewind(); + invalidate(); + } else if (fireActionPerformed) { + postDelayed(mFadingOut, mFadeOffset); + } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) { + mFadingOut.resetMultipleStrokes = true; + postDelayed(mFadingOut, mFadeOffset); + } else { + mCurrentGesture = null; + mPath.rewind(); + invalidate(); + } + } + } + + public void cancelClearAnimation() { + setPaintAlpha(255); + mIsFadingOut = false; + mFadingHasStarted = false; + removeCallbacks(mFadingOut); + mPath.rewind(); + mCurrentGesture = null; + } + + public void cancelGesture() { + mIsListeningForGestures = false; + + // add the stroke to the current gesture + mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)); + + // pass the event to handlers + final long now = SystemClock.uptimeMillis(); + final MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + + final ArrayList listeners = mOnGestureListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureCancelled(this, event); + } + + event.recycle(); + + clear(false); + mIsGesturing = false; + mPreviousWasGesturing = false; + mStrokeBuffer.clear(); + + final ArrayList otherListeners = mOnGesturingListeners; + count = otherListeners.size(); + for (int i = 0; i < count; i++) { + otherListeners.get(i).onGesturingEnded(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + cancelClearAnimation(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (isEnabled() && mInputEnabled) { + final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null && + mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) && + mInterceptEvents; + + processEvent(event); + + if (cancelDispatch) { + event.setAction(MotionEvent.ACTION_CANCEL); + } + + super.dispatchTouchEvent(event); + + return true; + } + + return super.dispatchTouchEvent(event); + } + + private boolean processEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchDown(event); + invalidate(); + return true; + case MotionEvent.ACTION_MOVE: + if (mIsListeningForGestures) { + Rect rect = touchMove(event); + if (rect != null) { + invalidate(rect); + } + return true; + } + break; + case MotionEvent.ACTION_UP: + if (mIsListeningForGestures) { + touchUp(event, false); + invalidate(); + return true; + } + break; + case MotionEvent.ACTION_CANCEL: + if (mIsListeningForGestures) { + touchUp(event, true); + invalidate(); + return true; + } + } + + return false; + } + + private void touchDown(MotionEvent event) { + mIsListeningForGestures = true; + + float x = event.getX(); + float y = event.getY(); + + mX = x; + mY = y; + + mTotalLength = 0; + mIsGesturing = false; + + if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) { + if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); + mResetGesture = false; + mCurrentGesture = null; + mPath.rewind(); + } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) { + if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor); + } + + // if there is fading out going on, stop it. + if (mFadingHasStarted) { + cancelClearAnimation(); + } else if (mIsFadingOut || !mClearPerformedGesture) { + setPaintAlpha(255); + mIsFadingOut = false; + mFadingHasStarted = false; + removeCallbacks(mFadingOut); + } + + if (mCurrentGesture == null) { + mCurrentGesture = new Gesture(); + } + + mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + mPath.moveTo(x, y); + + final int border = mInvalidateExtraBorder; + mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border); + + mCurveEndX = x; + mCurveEndY = y; + + // pass the event to handlers + final ArrayList listeners = mOnGestureListeners; + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureStarted(this, event); + } + } + + private Rect touchMove(MotionEvent event) { + Rect areaToRefresh = null; + + final float x = event.getX(); + final float y = event.getY(); + + final float previousX = mX; + final float previousY = mY; + + final float dx = Math.abs(x - previousX); + final float dy = Math.abs(y - previousY); + + if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { + areaToRefresh = mInvalidRect; + + // start with the curve end + final int border = mInvalidateExtraBorder; + areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border, + (int) mCurveEndX + border, (int) mCurveEndY + border); + + float cX = mCurveEndX = (x + previousX) / 2; + float cY = mCurveEndY = (y + previousY) / 2; + + mPath.quadTo(previousX, previousY, cX, cY); + + // union with the control point of the new curve + areaToRefresh.union((int) previousX - border, (int) previousY - border, + (int) previousX + border, (int) previousY + border); + + // union with the end point of the new curve + areaToRefresh.union((int) cX - border, (int) cY - border, + (int) cX + border, (int) cY + border); + + mX = x; + mY = y; + + mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + if (mHandleGestureActions && !mIsGesturing) { + mTotalLength += (float) Math.hypot(dx, dy); + + if (mTotalLength > mGestureStrokeLengthThreshold) { + final OrientedBoundingBox box = + GestureUtils.computeOrientedBoundingBox(mStrokeBuffer); + + float angle = Math.abs(box.orientation); + if (angle > 90) { + angle = 180 - angle; + } + + if (box.squareness > mGestureStrokeSquarenessTreshold || + (mOrientation == ORIENTATION_VERTICAL ? + angle < mGestureStrokeAngleThreshold : + angle > mGestureStrokeAngleThreshold)) { + + mIsGesturing = true; + setCurrentColor(mCertainGestureColor); + + final ArrayList listeners = mOnGesturingListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGesturingStarted(this); + } + } + } + } + + // pass the event to handlers + final ArrayList listeners = mOnGestureListeners; + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGesture(this, event); + } + } + + return areaToRefresh; + } + + private void touchUp(MotionEvent event, boolean cancel) { + mIsListeningForGestures = false; + + // A gesture wasn't started or was cancelled + if (mCurrentGesture != null) { + // add the stroke to the current gesture + mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)); + + if (!cancel) { + // pass the event to handlers + final ArrayList listeners = mOnGestureListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureEnded(this, event); + } + + if (mClearPerformedGesture) + clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing, + false); + else if (mHandleGestureActions && mIsGesturing) { + mIsFadingOut = false; + postDelayed(mFadingOut, mFadeOffset); + } + } else { + cancelGesture(event); + + } + } else { + cancelGesture(event); + } + + mStrokeBuffer.clear(); + mPreviousWasGesturing = mIsGesturing; + mIsGesturing = false; + + final ArrayList listeners = mOnGesturingListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGesturingEnded(this); + } + } + + private void cancelGesture(MotionEvent event) { + // pass the event to handlers + final ArrayList listeners = mOnGestureListeners; + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureCancelled(this, event); + } + + clear(false); + } + + private void fireOnGesturePerformed() { + final ArrayList actionListeners = mOnGesturePerformedListeners; + final int count = actionListeners.size(); + for (int i = 0; i < count; i++) { + actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture); + } + } + + private class FadeOutRunnable implements Runnable { + boolean fireActionPerformed; + boolean resetMultipleStrokes; + + public void run() { + if (mIsFadingOut) { + final long now = AnimationUtils.currentAnimationTimeMillis(); + final long duration = now - mFadingStart; + + if (duration > mFadeDuration) { + if (fireActionPerformed) { + fireOnGesturePerformed(); + } + + mPreviousWasGesturing = false; + mIsFadingOut = false; + mFadingHasStarted = false; + mPath.rewind(); + mCurrentGesture = null; + setPaintAlpha(255); + } else { + mFadingHasStarted = true; + float interpolatedTime = Math.max(0.0f, + Math.min(1.0f, duration / (float) mFadeDuration)); + mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime); + setPaintAlpha((int) (255 * mFadingAlpha)); + postDelayed(this, FADE_ANIMATION_RATE); + } + } else if (resetMultipleStrokes) { + mResetGesture = true; + } else { + fireOnGesturePerformed(); + + mFadingHasStarted = false; + if (mClearPerformedGesture) { + mPath.rewind(); + mCurrentGesture = null; + mPreviousWasGesturing = false; + } else + mResetGesture = true; + setPaintAlpha(255); + } + + invalidate(); + } + } + + public static interface OnGesturingListener { + void onGesturingStarted(GestureOverlayView overlay); + + void onGesturingEnded(GestureOverlayView overlay); + } + + public static interface OnGestureListener { + void onGestureStarted(GestureOverlayView overlay, MotionEvent event); + + void onGesture(GestureOverlayView overlay, MotionEvent event); + + void onGestureEnded(GestureOverlayView overlay, MotionEvent event); + + void onGestureCancelled(GestureOverlayView overlay, MotionEvent event); + } + + public static interface OnGesturePerformedListener { + void onGesturePerformed(GestureOverlayView overlay, Gesture gesture); + } +} diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockGestureView.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockGestureView.java new file mode 100644 index 00000000..d8f5a0aa --- /dev/null +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockGestureView.java @@ -0,0 +1,181 @@ +package de.Maxr1998.xposed.maxlock.ui.lockscreen; + +import android.content.Context; +import android.gesture.Gesture; +import android.graphics.Color; +import android.util.AttributeSet; + +public class LockGestureView extends GestureOverlayView implements GestureOverlayView.OnGesturingListener, + GestureOverlayView.OnGesturePerformedListener { + protected int READY_COLOR = Color.BLUE; + protected int WRONG_COLOR = Color.RED; + protected int CORRECT_COLOR = Color.GREEN; + + private DisplayMode mGestureDisplayMode = DisplayMode.Ready; + private boolean mInStealthMode = false; + + private OnLockGestureListener mOnGestureListener; + + @Override + public void onGesturePerformed(GestureOverlayView gestureOverlayView, Gesture gesture) { + notifyGestureDetected(gesture); + } + + @Override + public void onGesturingStarted(GestureOverlayView gestureOverlayView) { + notifyGestureStart(); + } + + @Override + public void onGesturingEnded(GestureOverlayView gestureOverlayView) { + } + + /** + * The call back interface for detecting gestures entered by the user. + */ + public static interface OnLockGestureListener { + + /** + * A new gesture has begun. + */ + void onGestureStart(); + + /** + * The gesture was cleared. + */ + void onGestureCleared(); + + /** + * A gesture was detected from the user. + * @param gesture The gesture. + */ + void onGestureDetected(Gesture gesture); + } + + /** + * How to display the current pattern. + */ + public enum DisplayMode { + + /** + * The pattern drawn is correct (i.e draw it in a friendly color) + */ + Correct, + + /** + * The pattern is wrong (i.e draw a foreboding color) + */ + Wrong, + + Ready + } + + public LockGestureView(Context context) { + this(context, null); + } + + public LockGestureView(Context context, AttributeSet attrs) { + super(context, attrs); + setGestureVisible(true); + addOnGesturingListener(this); + addOnGesturePerformedListener(this); + setGestureColor(READY_COLOR); + setUncertainGestureColor(READY_COLOR); + mClearPerformedGesture = false; + } + + /** + * @return Whether the view is in stealth mode. + */ + public boolean isInStealthMode() { + return mInStealthMode; + } + + /** + * Set whether the view is in stealth mode. If true, there will be no + * visible feedback as the user enters the gesture. + * + * @param inStealthMode Whether in stealth mode. + */ + public void setInStealthMode(boolean inStealthMode) { + mInStealthMode = inStealthMode; + setGestureVisible(!inStealthMode); + } + + /** + * 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) { + mGestureDisplayMode = displayMode; + switch (displayMode) { + case Correct: + setGestureColor(CORRECT_COLOR); + break; + case Wrong: + setGestureColor(WRONG_COLOR); + break; + case Ready: + setGestureColor(READY_COLOR); + break; + } + invalidate(); + } + + /** + * Disable input (for instance when displaying a message that will + * timeout so user doesn't get view into messy state). + */ + public void disableInput() { + mInputEnabled = false; + } + + /** + * Enable input. + */ + public void enableInput() { + mInputEnabled = true; + } + + /** + * Clear the gesture. + */ + public void clearGesture() { + resetGesture(); + } + + /** + * Reset all pattern state. + */ + private void resetGesture() { + mGestureDisplayMode = DisplayMode.Correct; + clear(false); + invalidate(); + } + + /** + * Set the call back for gesture detection. + * @param onGestureListener The call back. + */ + public void setOnGestureListener( + OnLockGestureListener onGestureListener) { + mOnGestureListener = onGestureListener; + } + + private void notifyGestureStart() { + if (mOnGestureListener != null) + mOnGestureListener.onGestureStart(); + } + + private void notifyGestureCleared() { + if (mOnGestureListener != null) + mOnGestureListener.onGestureCleared(); + } + + private void notifyGestureDetected(Gesture gesture) { + if (mOnGestureListener != null) + mOnGestureListener.onGestureDetected(gesture); + } +} diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockView.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockView.java index d4665b85..f3693982 100644 --- a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockView.java +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/lockscreen/LockView.java @@ -23,6 +23,10 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; +import android.gesture.Gesture; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.Prediction; import android.graphics.Point; import android.os.Build; import android.support.annotation.DimenRes; @@ -46,6 +50,8 @@ import android.widget.TextView; import android.widget.Toast; +import java.io.File; +import java.util.ArrayList; import java.util.List; import de.Maxr1998.xposed.maxlock.Common; @@ -83,12 +89,18 @@ public LockView(Context context, String packageName, String activityName) { mActivityName = activityName; mLockingType = getPreferencesKeysPerApp(getContext()).getString(mPackageName, getPrefs().getString(Common.LOCKING_TYPE, "")); - - if (getPreferencesKeysPerApp(getContext()).contains(mPackageName)) { - mPassword = getPreferencesKeysPerApp(getContext()).getString(mPackageName + Common.APP_KEY_PREFERENCE, null); - } else - mPassword = getPreferencesKeys(getContext()).getString(Common.KEY_PREFERENCE, ""); - + if (mLockingType.equals(Common.PREF_VALUE_GESTURE)) { + if (getPreferencesKeysPerApp(getContext()).contains(mPackageName)) + mPassword = mPackageName + Common.APP_KEY_GESTURE; + else + mPassword = Common.MASTER_GESTURE_NAME; + } + else { + if (getPreferencesKeysPerApp(getContext()).contains(mPackageName)) + mPassword = getPreferencesKeysPerApp(getContext()).getString(mPackageName + Common.APP_KEY_PREFERENCE, null); + else + mPassword = getPreferencesKeys(getContext()).getString(Common.KEY_PREFERENCE, ""); + } // Dimensions ((Activity) getContext()).getWindowManager().getDefaultDisplay().getSize(screenSize); try { @@ -195,6 +207,10 @@ public void afterTextChanged(Editable editable) { mInputBar.setVisibility(View.GONE); mContainer.addView(new PatternView(getThemedContext(), this), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); break; + case Common.PREF_VALUE_GESTURE: + mInputBar.setVisibility(View.GONE); + mContainer.addView(new GestureActivityView(getThemedContext(), this), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + break; default: authenticationSucceededListener.onAuthenticationSucceeded(); return; @@ -267,6 +283,14 @@ public void setPattern(List pattern, PatternView patternView) { } } + public boolean checkInput(Gesture gesture) { + if (mPassword.equals("") || (gestureMatch(gesture, mPassword))) { + authenticationSucceededListener.onAuthenticationSucceeded(); + return true; + } + return false; + } + public boolean checkInput() { if (Util.shaHash(mCurrentKey.toString()).equals(mPassword) || mPassword.equals("")) { authenticationSucceededListener.onAuthenticationSucceeded(); @@ -275,6 +299,28 @@ public boolean checkInput() { return false; } + private boolean gestureMatch(Gesture gesture, String gestureName) { + File file = new File(getContext().getFilesDir(), Common.GESTURES_FILE); + GestureLibrary gestureStore = GestureLibraries.fromFile(file); + gestureStore.load(); + ArrayList predictions = gestureStore.recognize(gesture); + float minScore = 2.0f; + if (predictions.size() > 0) { + Prediction prediction = predictions.get(0); + if (prediction.score > minScore) { + if (prediction.name.equals(gestureName)) { + Gesture foundGesture = gestureStore.getGestures(gestureName).get(0); + if (foundGesture.getStrokesCount() == gesture.getStrokesCount()) { + return true; + } + else + return false; + } + } + } + return false; + } + @Override public void onClick(View view) { switch (view.getId()) { @@ -320,4 +366,4 @@ private boolean isTablet() { public boolean isLandscape() { return screenSize.x > screenSize.y; } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/MaxLockPreferenceFragment.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/MaxLockPreferenceFragment.java index 8911b682..f0db4f41 100644 --- a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/MaxLockPreferenceFragment.java +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/MaxLockPreferenceFragment.java @@ -73,6 +73,7 @@ import de.Maxr1998.xposed.maxlock.R; import de.Maxr1998.xposed.maxlock.ui.SettingsActivity; import de.Maxr1998.xposed.maxlock.ui.settings.applist.AppListFragment; +import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.GestureSetupFragment; import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.KnockCodeSetupFragment; import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.PinSetupFragment; import de.Maxr1998.xposed.maxlock.util.MLPreferences; @@ -380,6 +381,9 @@ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preferen Intent intent = new Intent(LockPatternActivity.ACTION_CREATE_PATTERN, null, getActivity(), LockPatternActivity.class); startActivityForResult(intent, Util.getPatternCode(-1)); return true; + case Common.LOCKING_TYPE_GESTURE: + launchFragment(new GestureSetupFragment(), false, this); + return true; } break; case OPTIONS: diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/applist/AppListAdapter.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/applist/AppListAdapter.java index ce849216..862b5823 100644 --- a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/applist/AppListAdapter.java +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/applist/AppListAdapter.java @@ -25,6 +25,9 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.GestureStore; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -54,6 +57,7 @@ import com.haibison.android.lockpattern.LockPatternActivity; import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -61,6 +65,7 @@ import de.Maxr1998.xposed.maxlock.Common; import de.Maxr1998.xposed.maxlock.R; import de.Maxr1998.xposed.maxlock.ui.settings.MaxLockPreferenceFragment; +import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.GestureSetupFragment; import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.KnockCodeSetupFragment; import de.Maxr1998.xposed.maxlock.ui.settings.lockingtype.PinSetupFragment; import de.Maxr1998.xposed.maxlock.util.MLPreferences; @@ -76,6 +81,7 @@ public class AppListAdapter extends RecyclerView.Adapter mItemList; private List mListBackup; private AlertDialog dialog; + private GestureLibrary mGestureStore; public AppListAdapter(Fragment fragment, List list) { mFragment = fragment; @@ -84,6 +90,8 @@ public AppListAdapter(Fragment fragment, List list) { prefsApps = MLPreferences.getPrefsApps(mContext); prefsKeysPerApp = mContext.getSharedPreferences(Common.PREFS_KEYS_PER_APP, Context.MODE_PRIVATE); mFilter = new AppFilter(); + mGestureStore = GestureLibraries.fromFile(new File(mContext.getFilesDir() + Common.GESTURES_FILE)); + mGestureStore.load(); } @Override @@ -142,7 +150,8 @@ public void onClick(View v) { mContext.getString(R.string.pref_locking_type_password), mContext.getString(R.string.pref_locking_type_pin), mContext.getString(R.string.pref_locking_type_knockcode), - mContext.getString(R.string.pref_locking_type_pattern) + mContext.getString(R.string.pref_locking_type_pattern), + mContext.getString(R.string.pref_locking_type_gesture) }; choose_lock.setItems(cs, new DialogInterface.OnClickListener() { @Override @@ -163,6 +172,8 @@ public void onClick(DialogInterface dialogInterface, int i) { Intent intent = new Intent(LockPatternActivity.ACTION_CREATE_PATTERN, null, mContext, LockPatternActivity.class); mFragment.startActivityForResult(intent, Util.getPatternCode(hld.getAdapterPosition())); return; + case 4: + frag = new GestureSetupFragment(); } Bundle b = new Bundle(1); b.putString(Common.INTENT_EXTRAS_CUSTOM_APP, key); @@ -170,8 +181,12 @@ public void onClick(DialogInterface dialogInterface, int i) { MaxLockPreferenceFragment.launchFragment(frag, false, mFragment); } }).show(); - } else + } else { prefsKeysPerApp.edit().remove(key).remove(key + Common.APP_KEY_PREFERENCE).apply(); + //remove the gesture too + mGestureStore.removeEntry(key + Common.APP_KEY_GESTURE); + mGestureStore.save(); + } } }); diff --git a/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/lockingtype/GestureSetupFragment.java b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/lockingtype/GestureSetupFragment.java new file mode 100644 index 00000000..85e536a8 --- /dev/null +++ b/app/src/main/java/de/Maxr1998/xposed/maxlock/ui/settings/lockingtype/GestureSetupFragment.java @@ -0,0 +1,189 @@ +/* + * MaxLock, an Xposed applock module for Android + * Copyright (C) 2014-2016 Max Rumpf alias Maxr1998 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.Maxr1998.xposed.maxlock.ui.settings.lockingtype; + +import android.content.Context; +import android.content.SharedPreferences; +import android.gesture.Gesture; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.Prediction; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; + +import de.Maxr1998.xposed.maxlock.Common; +import de.Maxr1998.xposed.maxlock.R; +import de.Maxr1998.xposed.maxlock.ui.lockscreen.LockGestureView; +import de.Maxr1998.xposed.maxlock.util.Util; + +public class GestureSetupFragment extends Fragment implements View.OnClickListener, LockGestureView.OnLockGestureListener { + + private String customApp; + private LockGestureView mLockGestureView; + private String mUiStage = "first"; + private Gesture mChosenGesture; + private File mGestureStoreFile; + private GestureLibrary mGestureStore; + private Button mNextButton; + private TextView mDescView; + private SharedPreferences prefs, prefsKey, prefsPerApp; + public static float LENGTH_THRESHOLD = 60.0f; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + // Prefs + prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + prefsKey = getActivity().getSharedPreferences(Common.PREFS_KEY, Context.MODE_PRIVATE); + prefsPerApp = getActivity().getSharedPreferences(Common.PREFS_KEYS_PER_APP, Context.MODE_PRIVATE); + mGestureStoreFile = new File(getActivity().getFilesDir() + Common.GESTURES_FILE); + mGestureStore = GestureLibraries.fromFile(mGestureStoreFile); + mGestureStore.load(); + // Strings + if (getArguments() != null) + customApp = getArguments().getString(Common.INTENT_EXTRAS_CUSTOM_APP); + + mGestureStore.load(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_gesture_setup, container, false); + mDescView = (TextView) rootView.findViewById(R.id.description); + mLockGestureView = (LockGestureView) rootView.findViewById(R.id.gestures_overlay); + mLockGestureView.setOnGestureListener(this); + Button mCancelButton = (Button) rootView.findViewById(R.id.button_cancel); + mCancelButton.setOnClickListener(this); + mNextButton = (Button) rootView.findViewById(R.id.button_positive); + mNextButton.setOnClickListener(this); + return rootView; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setTitle(getString(R.string.pref_locking_type_gesture)); + //noinspection ConstantConditions + ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.button_positive: + handleStage(); + break; + case R.id.button_cancel: + mLockGestureView.clearGesture(); + getActivity().onBackPressed(); + break; + } + } + + private void handleStage() { + if (mUiStage.equals("first")) { + mLockGestureView.clearGesture(); + mUiStage = "second"; + mDescView.setText(R.string.confirm_gesture); + if (customApp != null) { + if (mGestureStore.getGestures(customApp + Common.APP_KEY_GESTURE) != null) + mGestureStore.removeEntry(customApp + Common.APP_KEY_GESTURE); + mGestureStore.addGesture(customApp + Common.APP_KEY_GESTURE, mChosenGesture); + } else { + if (mGestureStore.getGestures(Common.MASTER_GESTURE_NAME)!=null) + mGestureStore.removeEntry(Common.MASTER_GESTURE_NAME); + mGestureStore.addGesture(Common.MASTER_GESTURE_NAME, mChosenGesture); + } + } else if (mUiStage.equals("second")) { + if (customApp!=null) { + if (gestureMatch(mChosenGesture, customApp + Common.APP_KEY_GESTURE)) { + mGestureStore.save(); + mGestureStoreFile.setReadable(true, false); + prefsPerApp.edit().putString(customApp, Common.PREF_VALUE_GESTURE).apply(); + } else { + Toast.makeText(getActivity(), R.string.toast_password_inconsistent, Toast.LENGTH_SHORT).show(); + } + } else { + if (gestureMatch(mChosenGesture, Common.MASTER_GESTURE_NAME)) { + mGestureStore.save(); + mGestureStoreFile.setReadable(true, false); + prefs.edit().putString(Common.LOCKING_TYPE, Common.PREF_VALUE_GESTURE).apply(); + } else { + Toast.makeText(getActivity(), R.string.toast_password_inconsistent, Toast.LENGTH_SHORT).show(); + } + } + getActivity().onBackPressed(); + } + } + + private boolean gestureMatch(Gesture gesture, String gestureName) { + ArrayList predictions = mGestureStore.recognize(gesture); + float minScore = 2.0f; + if (predictions.size() > 0) { + Prediction prediction = predictions.get(0); + if (prediction.score > minScore) { + if (prediction.name.equals(gestureName)) { + Gesture foundGesture = mGestureStore.getGestures(gestureName).get(0); + if (foundGesture.getStrokesCount() == gesture.getStrokesCount()) + return true; + else + return false; + } + } + } + return false; + } + + @Override + public void onGestureStart() { + + } + + @Override + public void onGestureCleared() { + + } + + @Override + public void onGestureDetected(Gesture gesture) { + if (gesture.getLength() + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 79e3cb3c..c5fbd6be 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -211,5 +211,4 @@ Dieser Admin ist vor Deaktivierung geschützt. Du kannst in innerhalb der MaxLock App mit der Option \"%1$s\" deaktivieren. - \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 450760d8..3ed87c93 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -190,5 +190,4 @@ MasterSwitch deshabilitado MasterSwitch habilitado No se especificó un tipo de bloqueo - \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b889996d..f9e4b103 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -213,5 +213,5 @@ 此管理保护被禁用!您可以从MaxLock应用程序内点击\"%1$s\"选项时禁用它 - + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 09b549c6..4133ca18 100755 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -9,4 +9,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f6deea1..3c4a66ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,6 +63,11 @@ Choose your knock code Knock code must be a least 4 touches Confirm your knock code + + Gesture + Confirm your gesture + Draw your gesture + Gesture Only Lockscreen UI Open Theme Manager diff --git a/app/src/main/res/xml/preferences_locking_type.xml b/app/src/main/res/xml/preferences_locking_type.xml index 0b55bcf7..59ab694a 100644 --- a/app/src/main/res/xml/preferences_locking_type.xml +++ b/app/src/main/res/xml/preferences_locking_type.xml @@ -14,6 +14,9 @@ + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_locking_ui.xml b/app/src/main/res/xml/preferences_locking_ui.xml index 9cfef1e4..9747da7c 100644 --- a/app/src/main/res/xml/preferences_locking_ui.xml +++ b/app/src/main/res/xml/preferences_locking_ui.xml @@ -82,6 +82,13 @@ android:key="haptic_feedback" android:title="@string/pref_haptic_feedback" /> + + + +