1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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 15 */ 16 17 package com.android.settings.biometrics.face; 18 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; 30 31 import com.android.settings.biometrics.BiometricEnrollSidecar; 32 33 /** 34 * A drawable containing the circle cutout as well as the animations. 35 */ 36 public class FaceEnrollAnimationDrawable extends Drawable 37 implements BiometricEnrollSidecar.Listener { 38 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; 42 43 private final Context mContext; 44 private final ParticleCollection.Listener mListener; 45 private Rect mBounds; 46 private final Paint mSquarePaint; 47 private final Paint mCircleCutoutPaint; 48 49 private ParticleCollection mParticleCollection; 50 51 private TimeAnimator mTimeAnimator; 52 53 private final ParticleCollection.Listener mAnimationListener 54 = new ParticleCollection.Listener() { 55 @Override 56 public void onEnrolled() { 57 if (mTimeAnimator != null && mTimeAnimator.isStarted()) { 58 mTimeAnimator.end(); 59 mListener.onEnrolled(); 60 } 61 } 62 }; 63 FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener listener)64 public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener listener) { 65 mContext = context; 66 mListener = listener; 67 68 mSquarePaint = new Paint(); 69 mSquarePaint.setColor(Color.WHITE); 70 mSquarePaint.setAntiAlias(true); 71 72 mCircleCutoutPaint = new Paint(); 73 mCircleCutoutPaint.setColor(Color.TRANSPARENT); 74 mCircleCutoutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 75 mCircleCutoutPaint.setAntiAlias(true); 76 } 77 78 @Override onEnrollmentHelp(int helpMsgId, CharSequence helpString)79 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { 80 mParticleCollection.onEnrollmentHelp(helpMsgId, helpString); 81 } 82 83 @Override onEnrollmentError(int errMsgId, CharSequence errString)84 public void onEnrollmentError(int errMsgId, CharSequence errString) { 85 mParticleCollection.onEnrollmentError(errMsgId, errString); 86 } 87 88 @Override onEnrollmentProgressChange(int steps, int remaining)89 public void onEnrollmentProgressChange(int steps, int remaining) { 90 mParticleCollection.onEnrollmentProgressChange(steps, remaining); 91 } 92 93 @Override onBoundsChange(Rect bounds)94 protected void onBoundsChange(Rect bounds) { 95 mBounds = bounds; 96 mParticleCollection = 97 new ParticleCollection(mContext, mAnimationListener, bounds, BORDER_BOUNDS); 98 99 if (mTimeAnimator == null) { 100 mTimeAnimator = new TimeAnimator(); 101 mTimeAnimator.setTimeListener((animation, totalTimeMs, deltaTimeMs) -> { 102 mParticleCollection.update(totalTimeMs, deltaTimeMs); 103 FaceEnrollAnimationDrawable.this.invalidateSelf(); 104 }); 105 mTimeAnimator.start(); 106 } 107 } 108 109 @Override draw(Canvas canvas)110 public void draw(Canvas canvas) { 111 if (mBounds == null) { 112 return; 113 } 114 canvas.save(); 115 116 // Draw a rectangle covering the whole view 117 canvas.drawRect(0, 0, mBounds.width(), mBounds.height(), mSquarePaint); 118 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); 122 123 // Draw the animation 124 mParticleCollection.draw(canvas); 125 126 canvas.restore(); 127 } 128 129 @Override setAlpha(int alpha)130 public void setAlpha(int alpha) { 131 132 } 133 134 @Override setColorFilter(ColorFilter colorFilter)135 public void setColorFilter(ColorFilter colorFilter) { 136 137 } 138 139 @Override getOpacity()140 public int getOpacity() { 141 return PixelFormat.TRANSLUCENT; 142 } 143 } 144