diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 3f756d7577066..e4385a697c85a 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -19,16 +19,20 @@ package com.android.internal.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Bundle;
@@ -70,7 +74,9 @@ public class LockPatternView extends View {
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
private static final boolean PROFILE_DRAWING = false;
- private static final float LINE_FADE_ALPHA_MULTIPLIER = 1.5f;
+ private static final int LINE_END_ANIMATION_DURATION_MILLIS = 50;
+ private static final int LINE_FADE_OUT_DURATION_MILLIS = 500;
+ private static final int LINE_FADE_OUT_DELAY_MILLIS = 150;
private final CellState[][] mCellStates;
private final int mDotSize;
@@ -139,6 +145,7 @@ public class LockPatternView extends View {
private float mSquareWidth;
@UnsupportedAppUsage
private float mSquareHeight;
+ private final LinearGradient mFadeOutGradientShader;
private final Path mCurrentPath = new Path();
private final Rect mInvalidate = new Rect();
@@ -232,7 +239,10 @@ public class LockPatternView extends View {
float alpha = 1f;
public float lineEndX = Float.MIN_VALUE;
public float lineEndY = Float.MIN_VALUE;
- public ValueAnimator lineAnimator;
+ @Nullable
+ ValueAnimator lineEndAnimator;
+ @Nullable
+ ValueAnimator lineDisappearingAnimator;
}
/**
@@ -361,6 +371,14 @@ public class LockPatternView extends View {
mExploreByTouchHelper = new PatternExploreByTouchHelper(this);
setAccessibilityDelegate(mExploreByTouchHelper);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+ int fadeAwayGradientWidth = getResources().getDimensionPixelSize(
+ R.dimen.lock_pattern_fade_away_gradient_width);
+ // Set up gradient shader with the middle in point (0, 0).
+ mFadeOutGradientShader = new LinearGradient(/* x0= */ -fadeAwayGradientWidth / 2f,
+ /* y0= */ 0,/* x1= */ fadeAwayGradientWidth / 2f, /* y1= */ 0,
+ Color.TRANSPARENT, pathColor, Shader.TileMode.CLAMP);
+
a.recycle();
}
@@ -791,8 +809,13 @@ public class LockPatternView extends View {
});
startLineEndAnimation(cellState, mInProgressX, mInProgressY,
getCenterXForColumn(cell.column), getCenterYForRow(cell.row));
+ startLineDisappearingAnimation(cellState);
}
+ /**
+ * On the last frame before cell activates the end point of in progress line is not aligned
+ * with dot center so we execute a short animation moving the end point to exact dot center.
+ */
private void startLineEndAnimation(final CellState state,
final float startX, final float startY, final float targetX, final float targetY) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
@@ -808,13 +831,37 @@ public class LockPatternView extends View {
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- state.lineAnimator = null;
+ state.lineEndAnimator = null;
}
});
valueAnimator.setInterpolator(mFastOutSlowInInterpolator);
- valueAnimator.setDuration(100);
+ valueAnimator.setDuration(LINE_END_ANIMATION_DURATION_MILLIS);
valueAnimator.start();
- state.lineAnimator = valueAnimator;
+ state.lineEndAnimator = valueAnimator;
+ }
+
+ /**
+ * Starts animator to fade out a line segment. It does only invalidate because all the
+ * transitions are applied in {@code onDraw} method.
+ */
+ private void startLineDisappearingAnimation(final CellState state) {
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
+ valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ invalidate();
+ }
+ });
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ state.lineDisappearingAnimator = null;
+ }
+ });
+ valueAnimator.setStartDelay(LINE_FADE_OUT_DELAY_MILLIS);
+ valueAnimator.setDuration(LINE_FADE_OUT_DURATION_MILLIS);
+ valueAnimator.start();
+ state.lineDisappearingAnimator = valueAnimator;
}
private void startRadiusAnimation(float start, float end, long duration,
@@ -1051,11 +1098,16 @@ public class LockPatternView extends View {
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();
+ if (state.lineEndAnimator != null) {
+ state.lineEndAnimator.cancel();
+ state.lineEndAnimator = null;
state.lineEndX = Float.MIN_VALUE;
state.lineEndY = Float.MIN_VALUE;
}
+ if (state.lineDisappearingAnimator != null) {
+ state.lineDisappearingAnimator.cancel();
+ state.lineDisappearingAnimator = null;
+ }
}
}
}
@@ -1195,29 +1247,19 @@ public class LockPatternView extends View {
float centerX = getCenterXForColumn(cell.column);
float centerY = getCenterYForRow(cell.row);
if (i != 0) {
- // Set this line segment to fade away animated.
- int lineFadeVal = (int) Math.min((elapsedRealtime -
- mLineFadeStart[i]) * LINE_FADE_ALPHA_MULTIPLIER, 255f);
-
CellState state = mCellStates[cell.row][cell.column];
currentPath.rewind();
- currentPath.moveTo(lastX, lastY);
+ float endX;
+ float endY;
if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) {
- currentPath.lineTo(state.lineEndX, state.lineEndY);
- if (mFadePattern) {
- mPathPaint.setAlpha((int) 255 - lineFadeVal );
- } else {
- mPathPaint.setAlpha(255);
- }
+ endX = state.lineEndX;
+ endY = state.lineEndY;
} else {
- currentPath.lineTo(centerX, centerY);
- if (mFadePattern) {
- mPathPaint.setAlpha((int) 255 - lineFadeVal );
- } else {
- mPathPaint.setAlpha(255);
- }
+ endX = centerX;
+ endY = centerY;
}
- canvas.drawPath(currentPath, mPathPaint);
+ drawLineSegment(canvas, /* startX = */ lastX, /* startY = */ lastY, endX, endY,
+ mLineFadeStart[i], elapsedRealtime);
}
lastX = centerX;
lastY = centerY;
@@ -1260,6 +1302,61 @@ public class LockPatternView extends View {
}
}
+ private void drawLineSegment(Canvas canvas, float startX, float startY, float endX, float endY,
+ long lineFadeStart, long elapsedRealtime) {
+ float fadeAwayProgress;
+ if (mFadePattern) {
+ if (elapsedRealtime - lineFadeStart
+ >= LINE_FADE_OUT_DELAY_MILLIS + LINE_FADE_OUT_DURATION_MILLIS) {
+ // Time for this segment animation is out so we don't need to draw it.
+ return;
+ }
+ // Set this line segment to fade away animated.
+ fadeAwayProgress = Math.max(
+ ((float) (elapsedRealtime - lineFadeStart - LINE_FADE_OUT_DELAY_MILLIS))
+ / LINE_FADE_OUT_DURATION_MILLIS, 0f);
+ drawFadingAwayLineSegment(canvas, startX, startY, endX, endY, fadeAwayProgress);
+ } else {
+ mPathPaint.setAlpha(255);
+ canvas.drawLine(startX, startY, endX, endY, mPathPaint);
+ }
+ }
+
+ private void drawFadingAwayLineSegment(Canvas canvas, float startX, float startY, float endX,
+ float endY, float fadeAwayProgress) {
+ mPathPaint.setAlpha((int) (255 * (1 - fadeAwayProgress)));
+
+ // To draw gradient segment we use mFadeOutGradientShader which has immutable coordinates
+ // thus we will need to translate and rotate the canvas.
+ mPathPaint.setShader(mFadeOutGradientShader);
+ canvas.save();
+
+ // First translate canvas to gradient middle point.
+ float gradientMidX = endX * fadeAwayProgress + startX * (1 - fadeAwayProgress);
+ float gradientMidY = endY * fadeAwayProgress + startY * (1 - fadeAwayProgress);
+ canvas.translate(gradientMidX, gradientMidY);
+
+ // Then rotate it to the direction of the segment.
+ double segmentAngleRad = Math.atan((endY - startY) / (endX - startX));
+ float segmentAngleDegrees = (float) Math.toDegrees(segmentAngleRad);
+ if (endX - startX < 0) {
+ // Arc tangent gives us angle degrees [-90; 90] thus to cover [90; 270] degrees we
+ // need this hack.
+ segmentAngleDegrees += 180f;
+ }
+ canvas.rotate(segmentAngleDegrees);
+
+ // Pythagoras theorem.
+ float segmentLength = (float) Math.hypot(endX - startX, endY - startY);
+
+ // Draw the segment in coordinates aligned with shader coordinates.
+ canvas.drawLine(/* startX= */ -segmentLength * fadeAwayProgress, /* startY= */
+ 0,/* stopX= */ segmentLength * (1 - fadeAwayProgress), /* stopY= */ 0, mPathPaint);
+
+ canvas.restore();
+ mPathPaint.setShader(null);
+ }
+
private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) {
float diffX = x - lastX;
float diffY = y - lastY;
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e8bb6067932ec..610c5910b6c18 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -641,6 +641,8 @@
22dp
14dp
30dp
+
+ 8dp
40dp
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b2f7e8227e7d0..7da5045f05823 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1318,6 +1318,7 @@
+