2 * Copyright (C) 2018 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
17 package com.android.settings.biometrics.face;
19 import android.animation.TimeAnimator;
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.ColorFilter;
24 import android.graphics.Paint;
25 import android.graphics.PixelFormat;
26 import android.graphics.PorterDuff;
27 import android.graphics.PorterDuffXfermode;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
31 import com.android.settings.biometrics.BiometricEnrollSidecar;
34 * A drawable containing the circle cutout as well as the animations.
36 public class FaceEnrollAnimationDrawable extends Drawable
37 implements BiometricEnrollSidecar.Listener {
39 // Tune this parameter so the UI looks nice - and so that we don't have to draw the animations
40 // outside our bounds. A fraction of each rotating dot should be overlapping the camera preview.
41 private static final int BORDER_BOUNDS = 20;
43 private final Context mContext;
44 private final ParticleCollection.Listener mListener;
46 private final Paint mSquarePaint;
47 private final Paint mCircleCutoutPaint;
49 private ParticleCollection mParticleCollection;
51 private TimeAnimator mTimeAnimator;
53 private final ParticleCollection.Listener mAnimationListener
54 = new ParticleCollection.Listener() {
56 public void onEnrolled() {
57 if (mTimeAnimator != null && mTimeAnimator.isStarted()) {
59 mListener.onEnrolled();
64 public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener listener) {
68 mSquarePaint = new Paint();
69 mSquarePaint.setColor(Color.WHITE);
70 mSquarePaint.setAntiAlias(true);
72 mCircleCutoutPaint = new Paint();
73 mCircleCutoutPaint.setColor(Color.TRANSPARENT);
74 mCircleCutoutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
75 mCircleCutoutPaint.setAntiAlias(true);
79 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
80 mParticleCollection.onEnrollmentHelp(helpMsgId, helpString);
84 public void onEnrollmentError(int errMsgId, CharSequence errString) {
85 mParticleCollection.onEnrollmentError(errMsgId, errString);
89 public void onEnrollmentProgressChange(int steps, int remaining) {
90 mParticleCollection.onEnrollmentProgressChange(steps, remaining);
94 protected void onBoundsChange(Rect bounds) {
97 new ParticleCollection(mContext, mAnimationListener, bounds, BORDER_BOUNDS);
99 if (mTimeAnimator == null) {
100 mTimeAnimator = new TimeAnimator();
101 mTimeAnimator.setTimeListener((animation, totalTimeMs, deltaTimeMs) -> {
102 mParticleCollection.update(totalTimeMs, deltaTimeMs);
103 FaceEnrollAnimationDrawable.this.invalidateSelf();
105 mTimeAnimator.start();
110 public void draw(Canvas canvas) {
111 if (mBounds == null) {
116 // Draw a rectangle covering the whole view
117 canvas.drawRect(0, 0, mBounds.width(), mBounds.height(), mSquarePaint);
119 // Clear a circle in the middle for the camera preview
120 canvas.drawCircle(mBounds.exactCenterX(), mBounds.exactCenterY(),
121 mBounds.height() / 2 - BORDER_BOUNDS, mCircleCutoutPaint);
123 // Draw the animation
124 mParticleCollection.draw(canvas);
130 public void setAlpha(int alpha) {
135 public void setColorFilter(ColorFilter colorFilter) {
140 public int getOpacity() {
141 return PixelFormat.TRANSLUCENT;