diff --git a/res/values/colors.xml b/res/values/colors.xml index e5f7c276ae9..f398d9225ce 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -136,5 +136,11 @@ #fdd835 #f44336 + + #ff00bcd4 + #ffef6c00 + #ff4caf50 + #fffdd835 + #ff9e9e9e diff --git a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java index 111fecd881e..cedbec1a0ac 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java +++ b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java @@ -37,7 +37,7 @@ import java.util.ArrayList; public abstract class BiometricEnrollSidecar extends InstrumentedFragment { public interface Listener { - void onEnrollmentHelp(CharSequence helpString); + void onEnrollmentHelp(int helpMsgId, CharSequence helpString); void onEnrollmentError(int errMsgId, CharSequence errString); void onEnrollmentProgressChange(int steps, int remaining); } @@ -82,7 +82,7 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { @Override public void send(Listener listener) { - listener.onEnrollmentHelp(helpString); + listener.onEnrollmentHelp(helpMsgId, helpString); } } @@ -174,7 +174,7 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { protected void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (mListener != null) { - mListener.onEnrollmentHelp(helpString); + mListener.onEnrollmentHelp(helpMsgId, helpString); } else { mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString)); } diff --git a/src/com/android/settings/biometrics/face/AnimationParticle.java b/src/com/android/settings/biometrics/face/AnimationParticle.java new file mode 100644 index 00000000000..a192e9f1428 --- /dev/null +++ b/src/com/android/settings/biometrics/face/AnimationParticle.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 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.android.settings.biometrics.face; + +import android.animation.ArgbEvaluator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.Log; + +import com.android.settings.R; + +import java.util.List; + +/** + * Class containing the state for an individual feedback dot / path. The dots are assigned colors + * based on their index. + */ +public class AnimationParticle { + + private static final String TAG = "AnimationParticle"; + + private static final int MIN_STROKE_WIDTH = 10; + private static final int MAX_STROKE_WIDTH = 20; // Be careful that this doesn't get clipped + private static final int FINAL_RING_STROKE_WIDTH = 15; + + private static final float ROTATION_SPEED_NORMAL = 0.8f; // radians per second, 1 = ~57 degrees + private static final float ROTATION_ACCELERATION_SPEED = 2.0f; + private static final float PULSE_SPEED_NORMAL = 1 * 2 * (float) Math.PI; // 1 cycle per second + private static final float RING_SWEEP_GROW_RATE_PRIMARY = 480; // degrees per second + private static final float RING_SWEEP_GROW_RATE_SECONDARY = 240; // degrees per second + private static final float RING_SIZE_FINALIZATION_TIME = 0.1f; // seconds + + private final Rect mBounds; // bounds for the canvas + private final int mBorderWidth; // amount of padding from the edges + private final ArgbEvaluator mEvaluator; + private final int mErrorColor; + private final int mIndex; + private final Listener mListener; + + private final Paint mPaint; + private final int mAssignedColor; + private final float mOffsetTimeSec; // stagger particle size to make a wave effect + + private int mLastAnimationState; + private int mAnimationState; + private float mCurrentSize = MIN_STROKE_WIDTH; + private float mCurrentAngle; // 0 is to the right, in radians + private float mRotationSpeed = ROTATION_SPEED_NORMAL; // speed of dot rotation + private float mSweepAngle = 0; // ring sweep, degrees per second + private float mSweepRate = RING_SWEEP_GROW_RATE_SECONDARY; // acceleration + private float mRingAdjustRate; // rate at which ring should grow/shrink to final size + private float mRingCompletionTime; // time at which ring should be completed + + public interface Listener { + void onRingCompleted(int index); + } + + public AnimationParticle(Context context, Listener listener, Rect bounds, int borderWidth, + int index, int totalParticles, List colors) { + mBounds = bounds; + mBorderWidth = borderWidth; + mEvaluator = new ArgbEvaluator(); + mErrorColor = context.getResources() + .getColor(R.color.face_anim_particle_error, context.getTheme()); + mIndex = index; + mListener = listener; + + mCurrentAngle = (float) index / totalParticles * 2 * (float) Math.PI; + mOffsetTimeSec = (float) index / totalParticles + * (1 / ROTATION_SPEED_NORMAL) * 2 * (float) Math.PI; + + mPaint = new Paint(); + mAssignedColor = colors.get(index % colors.size()); + mPaint.setColor(mAssignedColor); + mPaint.setAntiAlias(true); + mPaint.setStrokeWidth(mCurrentSize); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeCap(Paint.Cap.ROUND); + } + + public void updateState(int animationState) { + if (mAnimationState == animationState) { + Log.w(TAG, "Already in state " + animationState); + return; + } + if (animationState == ParticleCollection.STATE_COMPLETE) { + mPaint.setStyle(Paint.Style.STROKE); + } + mLastAnimationState = mAnimationState; + mAnimationState = animationState; + } + + // There are two types of particles, secondary and primary. Primary particles accelerate faster + // during the "completed" animation. Particles are secondary by default. + public void setAsPrimary() { + mSweepRate = RING_SWEEP_GROW_RATE_PRIMARY; + } + + public void update(long t, long dt) { + if (mAnimationState != ParticleCollection.STATE_COMPLETE) { + updateDot(t, dt); + } else { + updateRing(t, dt); + } + } + + private void updateDot(long t, long dt) { + final float dtSec = 0.001f * dt; + final float tSec = 0.001f * t; + + final float multiplier = mRotationSpeed / ROTATION_SPEED_NORMAL; + + // Calculate rotation speed / angle + if ((mAnimationState == ParticleCollection.STATE_STOPPED_COLORFUL + || mAnimationState == ParticleCollection.STATE_STOPPED_GRAY) + && mRotationSpeed > 0) { + // Linear slow down for now + mRotationSpeed = Math.max(mRotationSpeed - ROTATION_ACCELERATION_SPEED * dtSec, 0); + } else if (mAnimationState == ParticleCollection.STATE_STARTED + && mRotationSpeed < ROTATION_SPEED_NORMAL) { + // Linear speed up for now + mRotationSpeed += ROTATION_ACCELERATION_SPEED * dtSec; + } + + mCurrentAngle += dtSec * mRotationSpeed; + + // Calculate dot / ring size; linearly proportional with rotation speed + mCurrentSize = + (MAX_STROKE_WIDTH - MIN_STROKE_WIDTH) / 2 + * (float) Math.sin(tSec * PULSE_SPEED_NORMAL + mOffsetTimeSec) + + (MAX_STROKE_WIDTH + MIN_STROKE_WIDTH) / 2; + mCurrentSize = (mCurrentSize - MIN_STROKE_WIDTH) * multiplier + MIN_STROKE_WIDTH; + + // Calculate paint color; linearly proportional to rotation speed + int color = mAssignedColor; + if (mAnimationState == ParticleCollection.STATE_STOPPED_GRAY) { + color = (int) mEvaluator.evaluate(1 - multiplier, mAssignedColor, mErrorColor); + } else if (mLastAnimationState == ParticleCollection.STATE_STOPPED_GRAY) { + color = (int) mEvaluator.evaluate(1 - multiplier, mAssignedColor, mErrorColor); + } + + mPaint.setColor(color); + mPaint.setStrokeWidth(mCurrentSize); + } + + private void updateRing(long t, long dt) { + final float dtSec = 0.001f * dt; + final float tSec = 0.001f * t; + + // Store the start time, since we need to guarantee all rings reach final size at same time + // independent of current size. The magic 0 check is safe. + if (mRingAdjustRate == 0) { + mRingAdjustRate = + (FINAL_RING_STROKE_WIDTH - mCurrentSize) / RING_SIZE_FINALIZATION_TIME; + if (mRingCompletionTime == 0) { + mRingCompletionTime = tSec + RING_SIZE_FINALIZATION_TIME; + } + } + + // Accelerate to attack speed.. jk, back to normal speed + if (mRotationSpeed < ROTATION_SPEED_NORMAL) { + mRotationSpeed += ROTATION_ACCELERATION_SPEED * dtSec; + } + + // For arcs, this is the "start" + mCurrentAngle += dtSec * mRotationSpeed; + + // Update the sweep angle until it fills entire circle + if (mSweepAngle < 360) { + final float sweepGrowth = mSweepRate * dtSec; + mSweepAngle = mSweepAngle + sweepGrowth; + mSweepRate = mSweepRate + sweepGrowth; + } + if (mSweepAngle > 360) { + mSweepAngle = 360; + mListener.onRingCompleted(mIndex); + } + + // Animate stroke width to final size. + if (tSec < RING_SIZE_FINALIZATION_TIME) { + mCurrentSize = mCurrentSize + mRingAdjustRate * dtSec; + mPaint.setStrokeWidth(mCurrentSize); + } else { + // There should be small to no discontinuity in this if/else + mCurrentSize = FINAL_RING_STROKE_WIDTH; + mPaint.setStrokeWidth(mCurrentSize); + } + + } + + public void draw(Canvas canvas) { + if (mAnimationState != ParticleCollection.STATE_COMPLETE) { + drawDot(canvas); + } else { + drawRing(canvas); + } + } + + // Draws a dot at the current position on the circumference of the path. + private void drawDot(Canvas canvas) { + final float w = mBounds.right - mBounds.exactCenterX() - mBorderWidth; + final float h = mBounds.bottom - mBounds.exactCenterY() - mBorderWidth; + canvas.drawCircle( + mBounds.exactCenterX() + w * (float) Math.cos(mCurrentAngle), + mBounds.exactCenterY() + h * (float) Math.sin(mCurrentAngle), + mCurrentSize, + mPaint); + } + + private void drawRing(Canvas canvas) { + RectF arc = new RectF( + mBorderWidth, mBorderWidth, + mBounds.width() - mBorderWidth, mBounds.height() - mBorderWidth); + Path path = new Path(); + path.arcTo(arc, (float) Math.toDegrees(mCurrentAngle), mSweepAngle); + canvas.drawPath(path, mPaint); + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java index 0da666cc9f6..5be7c5331d3 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java @@ -16,6 +16,8 @@ package com.android.settings.biometrics.face; +import android.animation.TimeAnimator; +import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -26,16 +28,43 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; -/** - * A drawable containing the circle cutout. - */ -public class FaceEnrollAnimationDrawable extends Drawable { +import com.android.settings.biometrics.BiometricEnrollSidecar; +/** + * A drawable containing the circle cutout as well as the animations. + */ +public class FaceEnrollAnimationDrawable extends Drawable + implements BiometricEnrollSidecar.Listener { + + // Tune this parameter so the UI looks nice - and so that we don't have to draw the animations + // outside our bounds. A fraction of each rotating dot should be overlapping the camera preview. + private static final int BORDER_BOUNDS = 20; + + private final Context mContext; + private final ParticleCollection.Listener mListener; private Rect mBounds; private final Paint mSquarePaint; private final Paint mCircleCutoutPaint; - public FaceEnrollAnimationDrawable() { + private ParticleCollection mParticleCollection; + + private TimeAnimator mTimeAnimator; + + private final ParticleCollection.Listener mAnimationListener + = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + if (mTimeAnimator != null && mTimeAnimator.isStarted()) { + mTimeAnimator.end(); + mListener.onEnrolled(); + } + } + }; + + public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener listener) { + mContext = context; + mListener = listener; + mSquarePaint = new Paint(); mSquarePaint.setColor(Color.WHITE); mSquarePaint.setAntiAlias(true); @@ -46,9 +75,35 @@ public class FaceEnrollAnimationDrawable extends Drawable { mCircleCutoutPaint.setAntiAlias(true); } + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mParticleCollection.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mParticleCollection.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + mParticleCollection.onEnrollmentProgressChange(steps, remaining); + } + @Override protected void onBoundsChange(Rect bounds) { mBounds = bounds; + mParticleCollection = + new ParticleCollection(mContext, mAnimationListener, bounds, BORDER_BOUNDS); + + if (mTimeAnimator == null) { + mTimeAnimator = new TimeAnimator(); + mTimeAnimator.setTimeListener((animation, totalTimeMs, deltaTimeMs) -> { + mParticleCollection.update(totalTimeMs, deltaTimeMs); + FaceEnrollAnimationDrawable.this.invalidateSelf(); + }); + mTimeAnimator.start(); + } } @Override @@ -63,7 +118,10 @@ public class FaceEnrollAnimationDrawable extends Drawable { // Clear a circle in the middle for the camera preview canvas.drawCircle(mBounds.exactCenterX(), mBounds.exactCenterY(), - mBounds.height() / 2, mCircleCutoutPaint); + mBounds.height() / 2 - BORDER_BOUNDS, mCircleCutoutPaint); + + // Draw the animation + mParticleCollection.draw(canvas); canvas.restore(); } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java index 7fac9f6724f..fccb39a20ee 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java @@ -45,7 +45,14 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { private TextView mErrorText; private Interpolator mLinearOutSlowInInterpolator; private boolean mShouldFinishOnStop = true; - private FaceEnrollPreviewFragment mFaceCameraPreview; + private FaceEnrollPreviewFragment mPreviewFragment; + + private ParticleCollection.Listener mListener = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + FaceEnrollEnrolling.this.launchFinish(mToken); + } + }; public static class FaceErrorDialog extends BiometricErrorDialog { static FaceErrorDialog newInstance(CharSequence msg, int msgId) { @@ -87,7 +94,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { if (shouldLaunchConfirmLock()) { launchConfirmLock(R.string.security_settings_face_preference_title, - Utils.getFaceManagerOrNull(this).preEnroll()); + Utils.getFingerprintManagerOrNull(this).preEnroll()); mShouldFinishOnStop = false; } else { startEnrollment(); @@ -97,13 +104,14 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { @Override public void startEnrollment() { super.startEnrollment(); - mFaceCameraPreview = (FaceEnrollPreviewFragment) getSupportFragmentManager() + mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager() .findFragmentByTag(TAG_FACE_PREVIEW); - if (mFaceCameraPreview == null) { - mFaceCameraPreview = new FaceEnrollPreviewFragment(); - getSupportFragmentManager().beginTransaction().add(mFaceCameraPreview, TAG_FACE_PREVIEW) + if (mPreviewFragment == null) { + mPreviewFragment = new FaceEnrollPreviewFragment(); + getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW) .commitAllowingStateLoss(); } + mPreviewFragment.setListener(mListener); } @Override @@ -132,10 +140,11 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (!TextUtils.isEmpty(helpString)) { showError(helpString); } + mPreviewFragment.onEnrollmentHelp(helpMsgId, helpString); } @Override @@ -149,6 +158,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { msgId = R.string.security_settings_face_enroll_error_generic_dialog_message; break; } + mPreviewFragment.onEnrollmentError(errMsgId, errString); showErrorDialog(getText(msgId), errMsgId); } @@ -157,6 +167,8 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { if (DEBUG) { Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining); } + mPreviewFragment.onEnrollmentProgressChange(steps, remaining); + // TODO: Update the actual animation showError("Steps: " + steps + " Remaining: " + remaining); } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java index 8bb8b929f36..1861e10a585 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java @@ -38,6 +38,7 @@ import android.widget.ImageView; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.biometrics.BiometricEnrollSidecar; import com.android.settings.core.InstrumentedPreferenceFragment; import java.util.ArrayList; @@ -50,7 +51,8 @@ import java.util.List; * Fragment that contains the logic for showing and controlling the camera preview, circular * overlay, as well as the enrollment animations. */ -public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { +public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment + implements BiometricEnrollSidecar.Listener { private static final String TAG = "FaceEnrollPreviewFragment"; @@ -65,6 +67,7 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { private CameraCaptureSession mCaptureSession; private CaptureRequest mPreviewRequest; private Size mPreviewSize; + private ParticleCollection.Listener mListener; // View used to contain the circular cutout and enrollment animation drawable private ImageView mCircleView; @@ -75,6 +78,15 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { // Texture used for showing the camera preview private FaceSquareTextureView mTextureView; + // Listener sent to the animation drawable + private final ParticleCollection.Listener mAnimationListener + = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + mListener.onEnrolled(); + } + }; + private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @@ -185,7 +197,7 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { // Must disable hardware acceleration for this view, otherwise transparency breaks mCircleView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - mAnimationDrawable = new FaceEnrollAnimationDrawable(); + mAnimationDrawable = new FaceEnrollAnimationDrawable(getContext(), mAnimationListener); mCircleView.setImageDrawable(mAnimationDrawable); mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); @@ -212,6 +224,25 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { closeCamera(); } + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mAnimationDrawable.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mAnimationDrawable.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + mAnimationDrawable.onEnrollmentProgressChange(steps, remaining); + } + + public void setListener(ParticleCollection.Listener listener) { + mListener = listener; + } + /** * Sets up member variables related to camera. * diff --git a/src/com/android/settings/biometrics/face/ParticleCollection.java b/src/com/android/settings/biometrics/face/ParticleCollection.java new file mode 100644 index 00000000000..399beec3abd --- /dev/null +++ b/src/com/android/settings/biometrics/face/ParticleCollection.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 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.android.settings.biometrics.face; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Rect; + +import com.android.settings.R; +import com.android.settings.biometrics.BiometricEnrollSidecar; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Class that's used to create, maintain, and update the state of each animation particle. Particles + * should have their colors assigned based on their index. Particles are split into primary and + * secondary types - primary types animate twice as fast during the completion effect. The particles + * are updated/drawn in a special order so that the overlap is correct during the final completion + * effect. + */ +public class ParticleCollection implements BiometricEnrollSidecar.Listener { + + private static final String TAG = "AnimationController"; + + private static final int NUM_PARTICLES = 12; + + public static final int STATE_STARTED = 1; // dots are rotating + public static final int STATE_STOPPED_COLORFUL = 2; // dots are not rotating but colorful + public static final int STATE_STOPPED_GRAY = 3; // dots are not rotating and also gray (error) + public static final int STATE_COMPLETE = 4; // face is enrolled + + private final List mParticleList; + private final List mPrimariesInProgress; // primary particles not done animating yet + private int mState; + private Listener mListener; + + public interface Listener { + void onEnrolled(); + } + + private final AnimationParticle.Listener mParticleListener = new AnimationParticle.Listener() { + @Override + public void onRingCompleted(int index) { + final boolean wasEmpty = mPrimariesInProgress.isEmpty(); + // We can stop the time animator once the three primary particles have finished + for (int i = 0; i < mPrimariesInProgress.size(); i++) { + if (mPrimariesInProgress.get(i).intValue() == index) { + mPrimariesInProgress.remove(i); + break; + } + } + if (mPrimariesInProgress.isEmpty() && !wasEmpty) { + mListener.onEnrolled(); + } + } + }; + + public ParticleCollection(Context context, Listener listener, Rect bounds, int borderWidth) { + mParticleList = new ArrayList<>(); + mListener = listener; + + final List colors = new ArrayList<>(); + final Resources.Theme theme = context.getTheme(); + final Resources resources = context.getResources(); + colors.add(resources.getColor(R.color.face_anim_particle_color_1, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_2, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_3, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_4, theme)); + + // Primary particles expand faster during the completion animation + mPrimariesInProgress = new ArrayList<>(Arrays.asList(0, 4, 8)); + + // Order in which to draw the particles. This is so the final "completion" animation has + // the correct behavior. + final int[] order = {3, 7, 11, 2, 6, 10, 1, 5, 9, 0, 4, 8}; + + for (int i = 0; i < NUM_PARTICLES; i++) { + AnimationParticle particle = new AnimationParticle(context, mParticleListener, bounds, + borderWidth, order[i], NUM_PARTICLES, colors); + if (mPrimariesInProgress.contains(order[i])) { + particle.setAsPrimary(); + } + mParticleList.add(particle); + } + + updateState(STATE_STARTED); + } + + public void update(long t, long dt) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).update(t, dt); + } + } + + public void draw(Canvas canvas) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).draw(canvas); + } + } + + private void updateState(int state) { + if (mState != state) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).updateState(state); + } + mState = state; + } + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + if (remaining == 0) { + updateState(STATE_COMPLETE); + } + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 38ef2c1b7f4..e6f3b04ae67 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -245,7 +245,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (!TextUtils.isEmpty(helpString)) { mErrorText.removeCallbacks(mTouchAgainRunnable); showError(helpString); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 927b5eb905e..c104eb3de94 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -94,7 +94,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { } @Override