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