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