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