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.annotation.Nullable;
20 import android.app.Activity;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.os.CancellationSignal;
24 import android.os.Handler;
25 import android.os.UserHandle;
26 
27 import com.android.settings.core.InstrumentedFragment;
28 import com.android.settings.password.ChooseLockSettingsHelper;
29 
30 import java.util.ArrayList;
31 
32 /**
33  * Abstract sidecar fragment to handle the state around biometric enrollment. This sidecar manages
34  * the state of enrollment throughout the activity lifecycle so the app can continue after an
35  * event like rotation.
36  */
37 public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
38 
39     public interface Listener {
onEnrollmentHelp(int helpMsgId, CharSequence helpString)40         void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
onEnrollmentError(int errMsgId, CharSequence errString)41         void onEnrollmentError(int errMsgId, CharSequence errString);
onEnrollmentProgressChange(int steps, int remaining)42         void onEnrollmentProgressChange(int steps, int remaining);
43     }
44 
45     private int mEnrollmentSteps = -1;
46     private int mEnrollmentRemaining = 0;
47     private Listener mListener;
48     private boolean mEnrolling;
49     private Handler mHandler = new Handler();
50     private boolean mDone;
51     private ArrayList<QueuedEvent> mQueuedEvents;
52 
53     protected CancellationSignal mEnrollmentCancel;
54     protected byte[] mToken;
55     protected int mUserId;
56 
57     private abstract class QueuedEvent {
send(Listener listener)58         public abstract void send(Listener listener);
59     }
60 
61     private class QueuedEnrollmentProgress extends QueuedEvent {
62         int enrollmentSteps;
63         int remaining;
QueuedEnrollmentProgress(int enrollmentSteps, int remaining)64         public QueuedEnrollmentProgress(int enrollmentSteps, int remaining) {
65             this.enrollmentSteps = enrollmentSteps;
66             this.remaining = remaining;
67         }
68 
69         @Override
send(Listener listener)70         public void send(Listener listener) {
71             listener.onEnrollmentProgressChange(enrollmentSteps, remaining);
72         }
73     }
74 
75     private class QueuedEnrollmentHelp extends QueuedEvent {
76         int helpMsgId;
77         CharSequence helpString;
QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString)78         public QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString) {
79             this.helpMsgId = helpMsgId;
80             this.helpString = helpString;
81         }
82 
83         @Override
send(Listener listener)84         public void send(Listener listener) {
85             listener.onEnrollmentHelp(helpMsgId, helpString);
86         }
87     }
88 
89     private class QueuedEnrollmentError extends QueuedEvent {
90         int errMsgId;
91         CharSequence errString;
QueuedEnrollmentError(int errMsgId, CharSequence errString)92         public QueuedEnrollmentError(int errMsgId, CharSequence errString) {
93             this.errMsgId = errMsgId;
94             this.errString = errString;
95         }
96 
97         @Override
send(Listener listener)98         public void send(Listener listener) {
99             listener.onEnrollmentError(errMsgId, errString);
100         }
101     }
102 
103     private final Runnable mTimeoutRunnable = new Runnable() {
104         @Override
105         public void run() {
106             cancelEnrollment();
107         }
108     };
109 
BiometricEnrollSidecar()110     public BiometricEnrollSidecar() {
111         mQueuedEvents = new ArrayList<>();
112     }
113 
114     @Override
onCreate(@ullable Bundle savedInstanceState)115     public void onCreate(@Nullable Bundle savedInstanceState) {
116         super.onCreate(savedInstanceState);
117         setRetainInstance(true);
118     }
119 
120     @Override
onAttach(Activity activity)121     public void onAttach(Activity activity) {
122         super.onAttach(activity);
123         mToken = activity.getIntent().getByteArrayExtra(
124                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
125         mUserId = activity.getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
126     }
127 
128     @Override
onStart()129     public void onStart() {
130         super.onStart();
131         if (!mEnrolling) {
132             startEnrollment();
133         }
134     }
135 
136     @Override
onStop()137     public void onStop() {
138         super.onStop();
139         if (!getActivity().isChangingConfigurations()) {
140             cancelEnrollment();
141         }
142     }
143 
startEnrollment()144     protected void startEnrollment() {
145         mHandler.removeCallbacks(mTimeoutRunnable);
146         mEnrollmentSteps = -1;
147         mEnrollmentCancel = new CancellationSignal();
148         mEnrolling = true;
149     }
150 
cancelEnrollment()151     public boolean cancelEnrollment() {
152         mHandler.removeCallbacks(mTimeoutRunnable);
153         if (mEnrolling) {
154             mEnrollmentCancel.cancel();
155             mEnrolling = false;
156             mEnrollmentSteps = -1;
157             return true;
158         }
159         return false;
160     }
161 
onEnrollmentProgress(int remaining)162     protected void onEnrollmentProgress(int remaining) {
163         if (mEnrollmentSteps == -1) {
164             mEnrollmentSteps = remaining;
165         }
166         mEnrollmentRemaining = remaining;
167         mDone = remaining == 0;
168         if (mListener != null) {
169             mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
170         } else {
171             mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
172         }
173     }
174 
onEnrollmentHelp(int helpMsgId, CharSequence helpString)175     protected void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
176         if (mListener != null) {
177             mListener.onEnrollmentHelp(helpMsgId, helpString);
178         } else {
179             mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString));
180         }
181     }
182 
onEnrollmentError(int errMsgId, CharSequence errString)183     protected void onEnrollmentError(int errMsgId, CharSequence errString) {
184         if (mListener != null) {
185             mListener.onEnrollmentError(errMsgId, errString);
186         } else {
187             mQueuedEvents.add(new QueuedEnrollmentError(errMsgId, errString));
188         }
189         mEnrolling = false;
190     }
191 
setListener(Listener listener)192     public void setListener(Listener listener) {
193         mListener = listener;
194         if (mListener != null) {
195             for (int i=0; i<mQueuedEvents.size(); i++) {
196                 QueuedEvent event = mQueuedEvents.get(i);
197                 event.send(mListener);
198             }
199             mQueuedEvents.clear();
200         }
201     }
202 
getEnrollmentSteps()203     public int getEnrollmentSteps() {
204         return mEnrollmentSteps;
205     }
206 
getEnrollmentRemaining()207     public int getEnrollmentRemaining() {
208         return mEnrollmentRemaining;
209     }
210 
isDone()211     public boolean isDone() {
212         return mDone;
213     }
214 
isEnrolling()215     public boolean isEnrolling() {
216         return mEnrolling;
217     }
218 }
219