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;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.os.Bundle;
22 import android.os.CancellationSignal;
23 import android.os.Handler;
24 import android.os.UserHandle;
25 
26 import androidx.annotation.Nullable;
27 
28 import com.android.settings.core.InstrumentedFragment;
29 import com.android.settings.password.ChooseLockSettingsHelper;
30 
31 import java.util.ArrayList;
32 
33 /**
34  * Abstract sidecar fragment to handle the state around biometric enrollment. This sidecar manages
35  * the state of enrollment throughout the activity lifecycle so the app can continue after an
36  * event like rotation.
37  */
38 public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
39 
40     public interface Listener {
onEnrollmentHelp(int helpMsgId, CharSequence helpString)41         void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
onEnrollmentError(int errMsgId, CharSequence errString)42         void onEnrollmentError(int errMsgId, CharSequence errString);
onEnrollmentProgressChange(int steps, int remaining)43         void onEnrollmentProgressChange(int steps, int remaining);
44         /**
45          * Called when a fingerprint image has been acquired.
46          * @param isAcquiredGood whether the fingerprint image was good.
47          */
onAcquired(boolean isAcquiredGood)48         default void onAcquired(boolean isAcquiredGood) { }
49         /**
50          * Called when a pointer down event has occurred.
51          */
onUdfpsPointerDown(int sensorId)52         default void onUdfpsPointerDown(int sensorId) { }
53         /**
54          * Called when a pointer up event has occurred.
55          */
onUdfpsPointerUp(int sensorId)56         default void onUdfpsPointerUp(int sensorId) { }
57 
58         /**
59          * Called when udfps overlay is shown.
60          */
onUdfpsOverlayShown()61         default void onUdfpsOverlayShown() { }
62     }
63 
64     private int mEnrollmentSteps = -1;
65     private int mEnrollmentRemaining = 0;
66     private Listener mListener;
67     private boolean mEnrolling;
68     private Handler mHandler = new Handler();
69     private boolean mDone;
70     private ArrayList<QueuedEvent> mQueuedEvents;
71 
72     protected CancellationSignal mEnrollmentCancel;
73     protected byte[] mToken;
74     protected int mUserId;
75 
76     private abstract class QueuedEvent {
send(Listener listener)77         public abstract void send(Listener listener);
78     }
79 
80     private class QueuedEnrollmentProgress extends QueuedEvent {
81         int enrollmentSteps;
82         int remaining;
QueuedEnrollmentProgress(int enrollmentSteps, int remaining)83         public QueuedEnrollmentProgress(int enrollmentSteps, int remaining) {
84             this.enrollmentSteps = enrollmentSteps;
85             this.remaining = remaining;
86         }
87 
88         @Override
send(Listener listener)89         public void send(Listener listener) {
90             listener.onEnrollmentProgressChange(enrollmentSteps, remaining);
91         }
92     }
93 
94     private class QueuedEnrollmentHelp extends QueuedEvent {
95         int helpMsgId;
96         CharSequence helpString;
QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString)97         public QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString) {
98             this.helpMsgId = helpMsgId;
99             this.helpString = helpString;
100         }
101 
102         @Override
send(Listener listener)103         public void send(Listener listener) {
104             listener.onEnrollmentHelp(helpMsgId, helpString);
105         }
106     }
107 
108     private class QueuedEnrollmentError extends QueuedEvent {
109         int errMsgId;
110         CharSequence errString;
QueuedEnrollmentError(int errMsgId, CharSequence errString)111         public QueuedEnrollmentError(int errMsgId, CharSequence errString) {
112             this.errMsgId = errMsgId;
113             this.errString = errString;
114         }
115 
116         @Override
send(Listener listener)117         public void send(Listener listener) {
118             listener.onEnrollmentError(errMsgId, errString);
119         }
120     }
121 
122     private class QueuedAcquired extends QueuedEvent {
123         private final boolean isAcquiredGood;
124 
QueuedAcquired(boolean isAcquiredGood)125         public QueuedAcquired(boolean isAcquiredGood) {
126             this.isAcquiredGood = isAcquiredGood;
127         }
128 
129         @Override
send(Listener listener)130         public void send(Listener listener) {
131             listener.onAcquired(isAcquiredGood);
132         }
133     }
134 
135     private class QueuedUdfpsPointerDown extends QueuedEvent {
136         private final int sensorId;
137 
QueuedUdfpsPointerDown(int sensorId)138         QueuedUdfpsPointerDown(int sensorId) {
139             this.sensorId = sensorId;
140         }
141 
142         @Override
send(Listener listener)143         public void send(Listener listener) {
144             listener.onUdfpsPointerDown(sensorId);
145         }
146     }
147 
148     private class QueuedUdfpsPointerUp extends QueuedEvent {
149         private final int sensorId;
150 
QueuedUdfpsPointerUp(int sensorId)151         QueuedUdfpsPointerUp(int sensorId) {
152             this.sensorId = sensorId;
153         }
154 
155         @Override
send(Listener listener)156         public void send(Listener listener) {
157             listener.onUdfpsPointerUp(sensorId);
158         }
159     }
160 
161     private class QueuedUdfpsOverlayShown extends QueuedEvent {
162         @Override
send(Listener listener)163         public void send(Listener listener) {
164             listener.onUdfpsOverlayShown();
165         }
166     }
167 
168     private final Runnable mTimeoutRunnable = new Runnable() {
169         @Override
170         public void run() {
171             cancelEnrollment();
172         }
173     };
174 
BiometricEnrollSidecar()175     public BiometricEnrollSidecar() {
176         mQueuedEvents = new ArrayList<>();
177     }
178 
179     @Override
onCreate(@ullable Bundle savedInstanceState)180     public void onCreate(@Nullable Bundle savedInstanceState) {
181         super.onCreate(savedInstanceState);
182         setRetainInstance(true);
183     }
184 
185     @Override
onAttach(Activity activity)186     public void onAttach(Activity activity) {
187         super.onAttach(activity);
188         mToken = activity.getIntent().getByteArrayExtra(
189                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
190         mUserId = activity.getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
191     }
192 
193     @Override
onStart()194     public void onStart() {
195         super.onStart();
196         if (!mEnrolling) {
197             startEnrollment();
198         }
199     }
200 
201     @Override
onStop()202     public void onStop() {
203         super.onStop();
204         if (!getActivity().isChangingConfigurations()) {
205             cancelEnrollment();
206         }
207     }
208 
startEnrollment()209     protected void startEnrollment() {
210         mHandler.removeCallbacks(mTimeoutRunnable);
211         mEnrollmentSteps = -1;
212         mEnrollmentCancel = new CancellationSignal();
213         mEnrolling = true;
214     }
215 
cancelEnrollment()216     public boolean cancelEnrollment() {
217         mHandler.removeCallbacks(mTimeoutRunnable);
218         if (mEnrolling) {
219             mEnrollmentCancel.cancel();
220             mEnrolling = false;
221             mEnrollmentSteps = -1;
222             return true;
223         }
224         return false;
225     }
226 
onEnrollmentProgress(int remaining)227     protected void onEnrollmentProgress(int remaining) {
228         if (mEnrollmentSteps == -1) {
229             mEnrollmentSteps = remaining;
230         }
231         mEnrollmentRemaining = remaining;
232         mDone = remaining == 0;
233         if (mListener != null) {
234             mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
235         } else {
236             mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
237         }
238     }
239 
onEnrollmentHelp(int helpMsgId, CharSequence helpString)240     protected void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
241         if (mListener != null) {
242             mListener.onEnrollmentHelp(helpMsgId, helpString);
243         } else {
244             mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString));
245         }
246     }
247 
onEnrollmentError(int errMsgId, CharSequence errString)248     protected void onEnrollmentError(int errMsgId, CharSequence errString) {
249         if (mListener != null) {
250             mListener.onEnrollmentError(errMsgId, errString);
251         } else {
252             mQueuedEvents.add(new QueuedEnrollmentError(errMsgId, errString));
253         }
254         mEnrolling = false;
255     }
256 
onAcquired(boolean isAcquiredGood)257     protected void onAcquired(boolean isAcquiredGood) {
258         if (mListener != null) {
259             mListener.onAcquired(isAcquiredGood);
260         } else {
261             mQueuedEvents.add(new QueuedAcquired(isAcquiredGood));
262         }
263     }
264 
onUdfpsPointerDown(int sensorId)265     protected void onUdfpsPointerDown(int sensorId) {
266         if (mListener != null) {
267             mListener.onUdfpsPointerDown(sensorId);
268         } else {
269             mQueuedEvents.add(new QueuedUdfpsPointerDown(sensorId));
270         }
271     }
272 
onUdfpsPointerUp(int sensorId)273     protected void onUdfpsPointerUp(int sensorId) {
274         if (mListener != null) {
275             mListener.onUdfpsPointerUp(sensorId);
276         } else {
277             mQueuedEvents.add(new QueuedUdfpsPointerUp(sensorId));
278         }
279     }
280 
onUdfpsOverlayShown()281     protected void onUdfpsOverlayShown() {
282         if (mListener != null) {
283             mListener.onUdfpsOverlayShown();
284         } else {
285             mQueuedEvents.add(new QueuedUdfpsOverlayShown());
286         }
287     }
288 
setListener(Listener listener)289     public void setListener(Listener listener) {
290         mListener = listener;
291         if (mListener != null) {
292             for (int i=0; i<mQueuedEvents.size(); i++) {
293                 QueuedEvent event = mQueuedEvents.get(i);
294                 event.send(mListener);
295             }
296             mQueuedEvents.clear();
297         }
298     }
299 
getEnrollmentSteps()300     public int getEnrollmentSteps() {
301         return mEnrollmentSteps;
302     }
303 
getEnrollmentRemaining()304     public int getEnrollmentRemaining() {
305         return mEnrollmentRemaining;
306     }
307 
isDone()308     public boolean isDone() {
309         return mDone;
310     }
311 
isEnrolling()312     public boolean isEnrolling() {
313         return mEnrolling;
314     }
315 }
316