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.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Canvas; 22 import android.graphics.Rect; 23 24 import com.android.settings.R; 25 import com.android.settings.biometrics.BiometricEnrollSidecar; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 31 /** 32 * Class that's used to create, maintain, and update the state of each animation particle. Particles 33 * should have their colors assigned based on their index. Particles are split into primary and 34 * secondary types - primary types animate twice as fast during the completion effect. The particles 35 * are updated/drawn in a special order so that the overlap is correct during the final completion 36 * effect. 37 */ 38 public class ParticleCollection implements BiometricEnrollSidecar.Listener { 39 40 private static final String TAG = "AnimationController"; 41 42 private static final int NUM_PARTICLES = 12; 43 44 public static final int STATE_STARTED = 1; // dots are rotating 45 public static final int STATE_STOPPED_COLORFUL = 2; // dots are not rotating but colorful 46 public static final int STATE_STOPPED_GRAY = 3; // dots are not rotating and also gray (error) 47 public static final int STATE_COMPLETE = 4; // face is enrolled 48 49 private final List<AnimationParticle> mParticleList; 50 private final List<Integer> mPrimariesInProgress; // primary particles not done animating yet 51 private int mState; 52 private Listener mListener; 53 54 public interface Listener { onEnrolled()55 void onEnrolled(); 56 } 57 58 private final AnimationParticle.Listener mParticleListener = new AnimationParticle.Listener() { 59 @Override 60 public void onRingCompleted(int index) { 61 final boolean wasEmpty = mPrimariesInProgress.isEmpty(); 62 // We can stop the time animator once the three primary particles have finished 63 for (int i = 0; i < mPrimariesInProgress.size(); i++) { 64 if (mPrimariesInProgress.get(i).intValue() == index) { 65 mPrimariesInProgress.remove(i); 66 break; 67 } 68 } 69 if (mPrimariesInProgress.isEmpty() && !wasEmpty) { 70 mListener.onEnrolled(); 71 } 72 } 73 }; 74 ParticleCollection(Context context, Listener listener, Rect bounds, int borderWidth)75 public ParticleCollection(Context context, Listener listener, Rect bounds, int borderWidth) { 76 mParticleList = new ArrayList<>(); 77 mListener = listener; 78 79 final List<Integer> colors = new ArrayList<>(); 80 final Resources.Theme theme = context.getTheme(); 81 final Resources resources = context.getResources(); 82 colors.add(resources.getColor(R.color.face_anim_particle_color_1, theme)); 83 colors.add(resources.getColor(R.color.face_anim_particle_color_2, theme)); 84 colors.add(resources.getColor(R.color.face_anim_particle_color_3, theme)); 85 colors.add(resources.getColor(R.color.face_anim_particle_color_4, theme)); 86 87 // Primary particles expand faster during the completion animation 88 mPrimariesInProgress = new ArrayList<>(Arrays.asList(0, 4, 8)); 89 90 // Order in which to draw the particles. This is so the final "completion" animation has 91 // the correct behavior. 92 final int[] order = {3, 7, 11, 2, 6, 10, 1, 5, 9, 0, 4, 8}; 93 94 for (int i = 0; i < NUM_PARTICLES; i++) { 95 AnimationParticle particle = new AnimationParticle(context, mParticleListener, bounds, 96 borderWidth, order[i], NUM_PARTICLES, colors); 97 if (mPrimariesInProgress.contains(order[i])) { 98 particle.setAsPrimary(); 99 } 100 mParticleList.add(particle); 101 } 102 103 updateState(STATE_STARTED); 104 } 105 update(long t, long dt)106 public void update(long t, long dt) { 107 for (int i = 0; i < mParticleList.size(); i++) { 108 mParticleList.get(i).update(t, dt); 109 } 110 } 111 draw(Canvas canvas)112 public void draw(Canvas canvas) { 113 for (int i = 0; i < mParticleList.size(); i++) { 114 mParticleList.get(i).draw(canvas); 115 } 116 } 117 updateState(int state)118 private void updateState(int state) { 119 if (mState != state) { 120 for (int i = 0; i < mParticleList.size(); i++) { 121 mParticleList.get(i).updateState(state); 122 } 123 mState = state; 124 } 125 } 126 127 @Override onEnrollmentHelp(int helpMsgId, CharSequence helpString)128 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { 129 130 } 131 132 @Override onEnrollmentError(int errMsgId, CharSequence errString)133 public void onEnrollmentError(int errMsgId, CharSequence errString) { 134 135 } 136 137 @Override onEnrollmentProgressChange(int steps, int remaining)138 public void onEnrollmentProgressChange(int steps, int remaining) { 139 if (remaining == 0) { 140 updateState(STATE_COMPLETE); 141 } 142 } 143 } 144