blob: e66a8fa96298d84f8037391be94513678b53fdf0 [file] [log] [blame]
Kevin Chynaae4a152018-01-18 11:48:09 -08001/*
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
Kevin Chyne9275662018-07-23 16:42:06 -070017package com.android.systemui.biometrics;
Kevin Chynaae4a152018-01-18 11:48:09 -080018
Kevin Chyn42653e82018-01-19 14:15:46 -080019import android.content.Context;
Kevin Chynaae4a152018-01-18 11:48:09 -080020import android.content.pm.PackageManager;
Gus Prevasa7df7b22018-10-30 10:29:34 -040021import android.content.res.Configuration;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070022import android.hardware.biometrics.BiometricAuthenticator;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070023import android.hardware.biometrics.BiometricPrompt;
Kevin Chyn23289ef2018-11-28 16:32:36 -080024import android.hardware.biometrics.IBiometricServiceReceiverInternal;
Kevin Chynaae4a152018-01-18 11:48:09 -080025import android.os.Bundle;
Kevin Chyn42653e82018-01-19 14:15:46 -080026import android.os.Handler;
Kevin Chyne674e852019-04-24 12:39:40 -070027import android.os.Looper;
Kevin Chyn42653e82018-01-19 14:15:46 -080028import android.os.Message;
29import android.os.RemoteException;
Kevin Chynaae4a152018-01-18 11:48:09 -080030import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080031import android.view.WindowManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080032
Kevin Chyn42653e82018-01-19 14:15:46 -080033import com.android.internal.os.SomeArgs;
Kevin Chync94b7db2019-05-15 17:28:16 -070034import com.android.systemui.Dependency;
Kevin Chynaae4a152018-01-18 11:48:09 -080035import com.android.systemui.SystemUI;
Kevin Chync94b7db2019-05-15 17:28:16 -070036import com.android.systemui.keyguard.WakefulnessLifecycle;
Kevin Chynaae4a152018-01-18 11:48:09 -080037import com.android.systemui.statusbar.CommandQueue;
38
Kevin Chyne9275662018-07-23 16:42:06 -070039/**
40 * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
Kevin Chyna883fb52018-09-18 18:23:22 -070041 * BiometricDialogView).
Kevin Chyne9275662018-07-23 16:42:06 -070042 */
43public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks {
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -070044 private static final String TAG = "BiometricDialogImpl";
Kevin Chyn42653e82018-01-19 14:15:46 -080045 private static final boolean DEBUG = true;
46
Kevin Chyn5906c172018-07-23 15:43:02 -070047 private static final int MSG_SHOW_DIALOG = 1;
Kevin Chyne9275662018-07-23 16:42:06 -070048 private static final int MSG_BIOMETRIC_AUTHENTICATED = 2;
49 private static final int MSG_BIOMETRIC_HELP = 3;
50 private static final int MSG_BIOMETRIC_ERROR = 4;
51 private static final int MSG_HIDE_DIALOG = 5;
Kevin Chyn5906c172018-07-23 15:43:02 -070052 private static final int MSG_BUTTON_NEGATIVE = 6;
53 private static final int MSG_USER_CANCELED = 7;
54 private static final int MSG_BUTTON_POSITIVE = 8;
Kevin Chyne1912712019-01-04 14:22:34 -080055 private static final int MSG_TRY_AGAIN_PRESSED = 9;
Kevin Chyn42653e82018-01-19 14:15:46 -080056
Gus Prevasa7df7b22018-10-30 10:29:34 -040057 private SomeArgs mCurrentDialogArgs;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070058 private BiometricDialogView mCurrentDialog;
Kevin Chyn42653e82018-01-19 14:15:46 -080059 private WindowManager mWindowManager;
Kevin Chyn23289ef2018-11-28 16:32:36 -080060 private IBiometricServiceReceiverInternal mReceiver;
Kevin Chyn42653e82018-01-19 14:15:46 -080061 private boolean mDialogShowing;
Kevin Chyn5906c172018-07-23 15:43:02 -070062 private Callback mCallback = new Callback();
Kevin Chync94b7db2019-05-15 17:28:16 -070063 private WakefulnessLifecycle mWakefulnessLifecycle;
Kevin Chyn42653e82018-01-19 14:15:46 -080064
Kevin Chyne674e852019-04-24 12:39:40 -070065 private Handler mHandler = new Handler(Looper.getMainLooper()) {
Kevin Chyn42653e82018-01-19 14:15:46 -080066 @Override
67 public void handleMessage(Message msg) {
68 switch(msg.what) {
69 case MSG_SHOW_DIALOG:
Kevin Chyne1912712019-01-04 14:22:34 -080070 handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
71 null /* savedState */);
Kevin Chyn42653e82018-01-19 14:15:46 -080072 break;
Kevin Chyne674e852019-04-24 12:39:40 -070073 case MSG_BIOMETRIC_AUTHENTICATED: {
74 SomeArgs args = (SomeArgs) msg.obj;
75 handleBiometricAuthenticated((boolean) args.arg1 /* authenticated */,
76 (String) args.arg2 /* failureReason */);
77 args.recycle();
Kevin Chyn42653e82018-01-19 14:15:46 -080078 break;
Kevin Chyne674e852019-04-24 12:39:40 -070079 }
80 case MSG_BIOMETRIC_HELP: {
Kevin Chyne1912712019-01-04 14:22:34 -080081 SomeArgs args = (SomeArgs) msg.obj;
Kevin Chyn3b53d6f2019-05-01 11:49:05 -070082 handleBiometricHelp((String) args.arg1 /* message */);
Kevin Chyne1912712019-01-04 14:22:34 -080083 args.recycle();
Kevin Chyn42653e82018-01-19 14:15:46 -080084 break;
Kevin Chyne674e852019-04-24 12:39:40 -070085 }
Kevin Chyne9275662018-07-23 16:42:06 -070086 case MSG_BIOMETRIC_ERROR:
87 handleBiometricError((String) msg.obj);
Kevin Chyn42653e82018-01-19 14:15:46 -080088 break;
89 case MSG_HIDE_DIALOG:
90 handleHideDialog((Boolean) msg.obj);
91 break;
92 case MSG_BUTTON_NEGATIVE:
93 handleButtonNegative();
94 break;
95 case MSG_USER_CANCELED:
96 handleUserCanceled();
97 break;
98 case MSG_BUTTON_POSITIVE:
99 handleButtonPositive();
100 break;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800101 case MSG_TRY_AGAIN_PRESSED:
102 handleTryAgainPressed();
103 break;
104 default:
105 Log.w(TAG, "Unknown message: " + msg.what);
106 break;
Kevin Chyn42653e82018-01-19 14:15:46 -0800107 }
108 }
109 };
Kevin Chynaae4a152018-01-18 11:48:09 -0800110
Kevin Chyn5906c172018-07-23 15:43:02 -0700111 private class Callback implements DialogViewCallback {
112 @Override
113 public void onUserCanceled() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800114 mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700115 }
116
117 @Override
118 public void onErrorShown() {
119 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_DIALOG,
120 false /* userCanceled */), BiometricPrompt.HIDE_DIALOG_DELAY);
121 }
122
123 @Override
124 public void onNegativePressed() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800125 mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700126 }
127
128 @Override
129 public void onPositivePressed() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800130 mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
131 }
132
133 @Override
134 public void onTryAgainPressed() {
135 mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700136 }
137 }
138
Kevin Chync94b7db2019-05-15 17:28:16 -0700139 final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
140 @Override
141 public void onStartedGoingToSleep() {
142 if (mDialogShowing) {
143 if (DEBUG) Log.d(TAG, "User canceled due to screen off");
144 mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
145 }
146 }
147 };
148
Kevin Chynaae4a152018-01-18 11:48:09 -0800149 @Override
150 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800151 final PackageManager pm = mContext.getPackageManager();
152 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
153 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
154 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500155 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400156 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chync94b7db2019-05-15 17:28:16 -0700157 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
158 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400159 }
160 }
161
Kevin Chynaae4a152018-01-18 11:48:09 -0800162 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800163 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
164 int type, boolean requireConfirmation, int userId) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800165 if (DEBUG) {
166 Log.d(TAG, "showBiometricDialog, type: " + type
167 + ", requireConfirmation: " + requireConfirmation);
168 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800169 // Remove these messages as they are part of the previous client
Kevin Chyne9275662018-07-23 16:42:06 -0700170 mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
171 mHandler.removeMessages(MSG_BIOMETRIC_HELP);
172 mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
Kevin Chyn87f257a2018-11-27 16:26:07 -0800173 mHandler.removeMessages(MSG_HIDE_DIALOG);
Kevin Chyn42653e82018-01-19 14:15:46 -0800174 SomeArgs args = SomeArgs.obtain();
175 args.arg1 = bundle;
176 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700177 args.argi1 = type;
178 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800179 args.argi2 = userId;
Kevin Chyn42653e82018-01-19 14:15:46 -0800180 mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800181 }
182
183 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700184 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
185 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
186 + " reason: " + failureReason);
187
188 SomeArgs args = SomeArgs.obtain();
189 args.arg1 = authenticated;
190 args.arg2 = failureReason;
191 mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800192 }
193
194 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700195 public void onBiometricHelp(String message) {
196 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
Kevin Chyne1912712019-01-04 14:22:34 -0800197 SomeArgs args = SomeArgs.obtain();
198 args.arg1 = message;
Kevin Chyne1912712019-01-04 14:22:34 -0800199 mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800200 }
201
202 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700203 public void onBiometricError(String error) {
204 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
205 mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800206 }
207
208 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700209 public void hideBiometricDialog() {
210 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
Kevin Chyn42653e82018-01-19 14:15:46 -0800211 mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
212 }
213
Kevin Chyne1912712019-01-04 14:22:34 -0800214 private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
Gus Prevasa7df7b22018-10-30 10:29:34 -0400215 mCurrentDialogArgs = args;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700216 final int type = args.argi1;
Kevin Chyne1912712019-01-04 14:22:34 -0800217
Kevin Chyn4511eb42019-03-14 14:41:46 -0700218 // Create a new dialog but do not replace the current one yet.
219 BiometricDialogView newDialog;
Kevin Chyne1912712019-01-04 14:22:34 -0800220 if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
Kevin Chyn4511eb42019-03-14 14:41:46 -0700221 newDialog = new FingerprintDialogView(mContext, mCallback);
Kevin Chyne1912712019-01-04 14:22:34 -0800222 } else if (type == BiometricAuthenticator.TYPE_FACE) {
Kevin Chyn4511eb42019-03-14 14:41:46 -0700223 newDialog = new FaceDialogView(mContext, mCallback);
Kevin Chyne1912712019-01-04 14:22:34 -0800224 } else {
225 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800226 return;
227 }
Kevin Chyn4511eb42019-03-14 14:41:46 -0700228
229 if (DEBUG) Log.d(TAG, "handleShowDialog, "
230 + " savedState: " + savedState
231 + " mCurrentDialog: " + mCurrentDialog
232 + " newDialog: " + newDialog
233 + " type: " + type);
234
235 if (savedState != null) {
236 // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
237 // even though it may be removed / re-created again
238 newDialog.restoreState(savedState);
239 } else if (mCurrentDialog != null && mDialogShowing) {
240 // If somehow we're asked to show a dialog, the old one doesn't need to be animated
241 // away. This can happen if the app cancels and re-starts auth during configuration
242 // change. This is ugly because we also have to do things on onConfigurationChanged
243 // here.
244 mCurrentDialog.forceRemove();
245 }
246
Kevin Chyn23289ef2018-11-28 16:32:36 -0800247 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
Kevin Chyn4511eb42019-03-14 14:41:46 -0700248 newDialog.setBundle((Bundle) args.arg1);
249 newDialog.setRequireConfirmation((boolean) args.arg3);
250 newDialog.setUserId(args.argi2);
251 newDialog.setSkipIntro(skipAnimation);
252 mCurrentDialog = newDialog;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700253 mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
Kevin Chyn42653e82018-01-19 14:15:46 -0800254 mDialogShowing = true;
255 }
256
Kevin Chyne674e852019-04-24 12:39:40 -0700257 private void handleBiometricAuthenticated(boolean authenticated, String failureReason) {
Kevin Chyne1912712019-01-04 14:22:34 -0800258 if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
Kevin Chyne9275662018-07-23 16:42:06 -0700259
Kevin Chyne1912712019-01-04 14:22:34 -0800260 if (authenticated) {
261 mCurrentDialog.announceForAccessibility(
262 mContext.getResources()
263 .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
264 if (mCurrentDialog.requiresConfirmation()) {
Kevin Chyn078dcf12019-04-26 11:14:20 -0700265 mCurrentDialog.updateState(BiometricDialogView.STATE_PENDING_CONFIRMATION);
Kevin Chyne1912712019-01-04 14:22:34 -0800266 } else {
267 mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
268 mHandler.postDelayed(() -> {
269 handleHideDialog(false /* userCanceled */);
270 }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
271 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700272 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700273 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700274 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800275 }
276
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700277 private void handleBiometricHelp(String message) {
Kevin Chyne9275662018-07-23 16:42:06 -0700278 if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700279 mCurrentDialog.onHelpReceived(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800280 }
281
Kevin Chyne9275662018-07-23 16:42:06 -0700282 private void handleBiometricError(String error) {
283 if (DEBUG) Log.d(TAG, "handleBiometricError: " + error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800284 if (!mDialogShowing) {
285 if (DEBUG) Log.d(TAG, "Dialog already dismissed");
286 return;
287 }
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700288 mCurrentDialog.onErrorReceived(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800289 }
290
291 private void handleHideDialog(boolean userCanceled) {
Kevin Chyn87df0682018-04-10 19:29:23 -0700292 if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
Kevin Chyn42653e82018-01-19 14:15:46 -0800293 if (!mDialogShowing) {
294 // This can happen if there's a race and we get called from both
295 // onAuthenticated and onError, etc.
296 Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
297 return;
298 }
299 if (userCanceled) {
300 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700301 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
Kevin Chyn42653e82018-01-19 14:15:46 -0800302 } catch (RemoteException e) {
303 Log.e(TAG, "RemoteException when hiding dialog", e);
304 }
305 }
306 mReceiver = null;
Kevin Chyn42653e82018-01-19 14:15:46 -0800307 mDialogShowing = false;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700308 mCurrentDialog.startDismiss();
Kevin Chyn42653e82018-01-19 14:15:46 -0800309 }
310
311 private void handleButtonNegative() {
312 if (mReceiver == null) {
313 Log.e(TAG, "Receiver is null");
314 return;
315 }
316 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700317 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800318 } catch (RemoteException e) {
319 Log.e(TAG, "Remote exception when handling negative button", e);
320 }
321 handleHideDialog(false /* userCanceled */);
322 }
323
324 private void handleButtonPositive() {
325 if (mReceiver == null) {
326 Log.e(TAG, "Receiver is null");
327 return;
328 }
329 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700330 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800331 } catch (RemoteException e) {
332 Log.e(TAG, "Remote exception when handling positive button", e);
333 }
334 handleHideDialog(false /* userCanceled */);
335 }
336
Kevin Chyn42653e82018-01-19 14:15:46 -0800337 private void handleUserCanceled() {
338 handleHideDialog(true /* userCanceled */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800339 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400340
Kevin Chyn23289ef2018-11-28 16:32:36 -0800341 private void handleTryAgainPressed() {
342 try {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800343 mReceiver.onTryAgainPressed();
344 } catch (RemoteException e) {
345 Log.e(TAG, "RemoteException when handling try again", e);
346 }
347 }
348
Gus Prevasa7df7b22018-10-30 10:29:34 -0400349 @Override
350 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700351 super.onConfigurationChanged(newConfig);
352 final boolean wasShowing = mDialogShowing;
Kevin Chyne1912712019-01-04 14:22:34 -0800353
354 // Save the state of the current dialog (buttons showing, etc)
355 final Bundle savedState = new Bundle();
356 if (mCurrentDialog != null) {
357 mCurrentDialog.onSaveState(savedState);
358 }
359
Gus Prevasa7df7b22018-10-30 10:29:34 -0400360 if (mDialogShowing) {
361 mCurrentDialog.forceRemove();
Kevin Chyn02129b12018-11-01 16:47:12 -0700362 mDialogShowing = false;
Gus Prevasa7df7b22018-10-30 10:29:34 -0400363 }
Kevin Chyne1912712019-01-04 14:22:34 -0800364
Kevin Chyn02129b12018-11-01 16:47:12 -0700365 if (wasShowing) {
Kevin Chyne1912712019-01-04 14:22:34 -0800366 handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400367 }
368 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800369}