blob: 016b8fe93093f7c29100eda34b1212d8d8a4a6c4 [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;
27import android.os.Message;
28import android.os.RemoteException;
Kevin Chynaae4a152018-01-18 11:48:09 -080029import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080030import android.view.WindowManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080031
Kevin Chyn42653e82018-01-19 14:15:46 -080032import com.android.internal.os.SomeArgs;
Kevin Chynaae4a152018-01-18 11:48:09 -080033import com.android.systemui.SystemUI;
34import com.android.systemui.statusbar.CommandQueue;
35
Kevin Chyne9275662018-07-23 16:42:06 -070036/**
37 * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
Kevin Chyna883fb52018-09-18 18:23:22 -070038 * BiometricDialogView).
Kevin Chyne9275662018-07-23 16:42:06 -070039 */
40public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks {
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -070041 private static final String TAG = "BiometricDialogImpl";
Kevin Chyn42653e82018-01-19 14:15:46 -080042 private static final boolean DEBUG = true;
43
Kevin Chyn5906c172018-07-23 15:43:02 -070044 private static final int MSG_SHOW_DIALOG = 1;
Kevin Chyne9275662018-07-23 16:42:06 -070045 private static final int MSG_BIOMETRIC_AUTHENTICATED = 2;
46 private static final int MSG_BIOMETRIC_HELP = 3;
47 private static final int MSG_BIOMETRIC_ERROR = 4;
48 private static final int MSG_HIDE_DIALOG = 5;
Kevin Chyn5906c172018-07-23 15:43:02 -070049 private static final int MSG_BUTTON_NEGATIVE = 6;
50 private static final int MSG_USER_CANCELED = 7;
51 private static final int MSG_BUTTON_POSITIVE = 8;
Kevin Chyne1912712019-01-04 14:22:34 -080052 private static final int MSG_TRY_AGAIN_PRESSED = 9;
Kevin Chyn42653e82018-01-19 14:15:46 -080053
Gus Prevasa7df7b22018-10-30 10:29:34 -040054 private SomeArgs mCurrentDialogArgs;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070055 private BiometricDialogView mCurrentDialog;
Kevin Chyn42653e82018-01-19 14:15:46 -080056 private WindowManager mWindowManager;
Kevin Chyn23289ef2018-11-28 16:32:36 -080057 private IBiometricServiceReceiverInternal mReceiver;
Kevin Chyn42653e82018-01-19 14:15:46 -080058 private boolean mDialogShowing;
Kevin Chyn5906c172018-07-23 15:43:02 -070059 private Callback mCallback = new Callback();
Kevin Chyn42653e82018-01-19 14:15:46 -080060
61 private Handler mHandler = new Handler() {
62 @Override
63 public void handleMessage(Message msg) {
64 switch(msg.what) {
65 case MSG_SHOW_DIALOG:
Kevin Chyne1912712019-01-04 14:22:34 -080066 handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
67 null /* savedState */);
Kevin Chyn42653e82018-01-19 14:15:46 -080068 break;
Kevin Chyne9275662018-07-23 16:42:06 -070069 case MSG_BIOMETRIC_AUTHENTICATED:
Kevin Chyne1912712019-01-04 14:22:34 -080070 handleBiometricAuthenticated((boolean) msg.obj);
Kevin Chyn42653e82018-01-19 14:15:46 -080071 break;
Kevin Chyne9275662018-07-23 16:42:06 -070072 case MSG_BIOMETRIC_HELP:
Kevin Chyne1912712019-01-04 14:22:34 -080073 SomeArgs args = (SomeArgs) msg.obj;
74 handleBiometricHelp((String) args.arg1 /* message */,
75 (boolean) args.arg2 /* requireTryAgain */);
76 args.recycle();
Kevin Chyn42653e82018-01-19 14:15:46 -080077 break;
Kevin Chyne9275662018-07-23 16:42:06 -070078 case MSG_BIOMETRIC_ERROR:
79 handleBiometricError((String) msg.obj);
Kevin Chyn42653e82018-01-19 14:15:46 -080080 break;
81 case MSG_HIDE_DIALOG:
82 handleHideDialog((Boolean) msg.obj);
83 break;
84 case MSG_BUTTON_NEGATIVE:
85 handleButtonNegative();
86 break;
87 case MSG_USER_CANCELED:
88 handleUserCanceled();
89 break;
90 case MSG_BUTTON_POSITIVE:
91 handleButtonPositive();
92 break;
Kevin Chyn23289ef2018-11-28 16:32:36 -080093 case MSG_TRY_AGAIN_PRESSED:
94 handleTryAgainPressed();
95 break;
96 default:
97 Log.w(TAG, "Unknown message: " + msg.what);
98 break;
Kevin Chyn42653e82018-01-19 14:15:46 -080099 }
100 }
101 };
Kevin Chynaae4a152018-01-18 11:48:09 -0800102
Kevin Chyn5906c172018-07-23 15:43:02 -0700103 private class Callback implements DialogViewCallback {
104 @Override
105 public void onUserCanceled() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800106 mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700107 }
108
109 @Override
110 public void onErrorShown() {
111 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_DIALOG,
112 false /* userCanceled */), BiometricPrompt.HIDE_DIALOG_DELAY);
113 }
114
115 @Override
116 public void onNegativePressed() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800117 mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700118 }
119
120 @Override
121 public void onPositivePressed() {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800122 mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
123 }
124
125 @Override
126 public void onTryAgainPressed() {
127 mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700128 }
129 }
130
Kevin Chynaae4a152018-01-18 11:48:09 -0800131 @Override
132 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800133 final PackageManager pm = mContext.getPackageManager();
134 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
135 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
136 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500137 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400138 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
139 }
140 }
141
Kevin Chynaae4a152018-01-18 11:48:09 -0800142 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800143 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
144 int type, boolean requireConfirmation, int userId) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800145 if (DEBUG) {
146 Log.d(TAG, "showBiometricDialog, type: " + type
147 + ", requireConfirmation: " + requireConfirmation);
148 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800149 // Remove these messages as they are part of the previous client
Kevin Chyne9275662018-07-23 16:42:06 -0700150 mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
151 mHandler.removeMessages(MSG_BIOMETRIC_HELP);
152 mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
Kevin Chyn87f257a2018-11-27 16:26:07 -0800153 mHandler.removeMessages(MSG_HIDE_DIALOG);
Kevin Chyn42653e82018-01-19 14:15:46 -0800154 SomeArgs args = SomeArgs.obtain();
155 args.arg1 = bundle;
156 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700157 args.argi1 = type;
158 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800159 args.argi2 = userId;
Kevin Chyn42653e82018-01-19 14:15:46 -0800160 mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800161 }
162
163 @Override
Kevin Chyne1912712019-01-04 14:22:34 -0800164 public void onBiometricAuthenticated(boolean authenticated) {
165 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
166 mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800167 }
168
169 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700170 public void onBiometricHelp(String message) {
171 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
Kevin Chyne1912712019-01-04 14:22:34 -0800172 SomeArgs args = SomeArgs.obtain();
173 args.arg1 = message;
174 args.arg2 = false; // requireTryAgain
175 mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800176 }
177
178 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700179 public void onBiometricError(String error) {
180 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
181 mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800182 }
183
184 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700185 public void hideBiometricDialog() {
186 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
Kevin Chyn42653e82018-01-19 14:15:46 -0800187 mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
188 }
189
Kevin Chyne1912712019-01-04 14:22:34 -0800190 private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
Gus Prevasa7df7b22018-10-30 10:29:34 -0400191 mCurrentDialogArgs = args;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700192 final int type = args.argi1;
Kevin Chyne1912712019-01-04 14:22:34 -0800193
194 if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
195 mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
196 } else if (type == BiometricAuthenticator.TYPE_FACE) {
197 mCurrentDialog = new FaceDialogView(mContext, mCallback);
198 } else {
199 Log.e(TAG, "Unsupported type: " + type);
200 }
201
202 if (savedState != null) {
203 mCurrentDialog.restoreState(savedState);
204 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700205
Kevin Chyn87df0682018-04-10 19:29:23 -0700206 if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700207 + mCurrentDialog.isAnimatingAway() + " type: " + type);
208
209 if (mCurrentDialog.isAnimatingAway()) {
210 mCurrentDialog.forceRemove();
Kevin Chyn87df0682018-04-10 19:29:23 -0700211 } else if (mDialogShowing) {
Kevin Chyn42653e82018-01-19 14:15:46 -0800212 Log.w(TAG, "Dialog already showing");
213 return;
214 }
Kevin Chyn23289ef2018-11-28 16:32:36 -0800215 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700216 mCurrentDialog.setBundle((Bundle)args.arg1);
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800217 mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
218 mCurrentDialog.setUserId(args.argi2);
Kevin Chyn02129b12018-11-01 16:47:12 -0700219 mCurrentDialog.setSkipIntro(skipAnimation);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700220 mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
Kevin Chyn42653e82018-01-19 14:15:46 -0800221 mDialogShowing = true;
222 }
223
Kevin Chyne1912712019-01-04 14:22:34 -0800224 private void handleBiometricAuthenticated(boolean authenticated) {
225 if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
Kevin Chyne9275662018-07-23 16:42:06 -0700226
Kevin Chyne1912712019-01-04 14:22:34 -0800227 if (authenticated) {
228 mCurrentDialog.announceForAccessibility(
229 mContext.getResources()
230 .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
231 if (mCurrentDialog.requiresConfirmation()) {
232 mCurrentDialog.showConfirmationButton(true /* show */);
233 } else {
234 mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
235 mHandler.postDelayed(() -> {
236 handleHideDialog(false /* userCanceled */);
237 }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
238 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700239 } else {
Kevin Chyne1912712019-01-04 14:22:34 -0800240 handleBiometricHelp(mContext.getResources()
241 .getString(com.android.internal.R.string.biometric_not_recognized),
242 true /* requireTryAgain */);
243 mCurrentDialog.showTryAgainButton(true /* show */);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700244 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800245 }
246
Kevin Chyne1912712019-01-04 14:22:34 -0800247 private void handleBiometricHelp(String message, boolean requireTryAgain) {
Kevin Chyne9275662018-07-23 16:42:06 -0700248 if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
Kevin Chyne1912712019-01-04 14:22:34 -0800249 mCurrentDialog.showHelpMessage(message, requireTryAgain);
Kevin Chyn42653e82018-01-19 14:15:46 -0800250 }
251
Kevin Chyne9275662018-07-23 16:42:06 -0700252 private void handleBiometricError(String error) {
253 if (DEBUG) Log.d(TAG, "handleBiometricError: " + error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800254 if (!mDialogShowing) {
255 if (DEBUG) Log.d(TAG, "Dialog already dismissed");
256 return;
257 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700258 mCurrentDialog.showErrorMessage(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800259 }
260
261 private void handleHideDialog(boolean userCanceled) {
Kevin Chyn87df0682018-04-10 19:29:23 -0700262 if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
Kevin Chyn42653e82018-01-19 14:15:46 -0800263 if (!mDialogShowing) {
264 // This can happen if there's a race and we get called from both
265 // onAuthenticated and onError, etc.
266 Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
267 return;
268 }
269 if (userCanceled) {
270 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700271 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
Kevin Chyn42653e82018-01-19 14:15:46 -0800272 } catch (RemoteException e) {
273 Log.e(TAG, "RemoteException when hiding dialog", e);
274 }
275 }
276 mReceiver = null;
Kevin Chyn42653e82018-01-19 14:15:46 -0800277 mDialogShowing = false;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700278 mCurrentDialog.startDismiss();
Kevin Chyn42653e82018-01-19 14:15:46 -0800279 }
280
281 private void handleButtonNegative() {
282 if (mReceiver == null) {
283 Log.e(TAG, "Receiver is null");
284 return;
285 }
286 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700287 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800288 } catch (RemoteException e) {
289 Log.e(TAG, "Remote exception when handling negative button", e);
290 }
291 handleHideDialog(false /* userCanceled */);
292 }
293
294 private void handleButtonPositive() {
295 if (mReceiver == null) {
296 Log.e(TAG, "Receiver is null");
297 return;
298 }
299 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700300 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800301 } catch (RemoteException e) {
302 Log.e(TAG, "Remote exception when handling positive button", e);
303 }
304 handleHideDialog(false /* userCanceled */);
305 }
306
Kevin Chyn42653e82018-01-19 14:15:46 -0800307 private void handleUserCanceled() {
308 handleHideDialog(true /* userCanceled */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800309 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400310
Kevin Chyn23289ef2018-11-28 16:32:36 -0800311 private void handleTryAgainPressed() {
312 try {
313 mCurrentDialog.clearTemporaryMessage();
Kevin Chyn23289ef2018-11-28 16:32:36 -0800314 mReceiver.onTryAgainPressed();
315 } catch (RemoteException e) {
316 Log.e(TAG, "RemoteException when handling try again", e);
317 }
318 }
319
Gus Prevasa7df7b22018-10-30 10:29:34 -0400320 @Override
321 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700322 super.onConfigurationChanged(newConfig);
323 final boolean wasShowing = mDialogShowing;
Kevin Chyne1912712019-01-04 14:22:34 -0800324
325 // Save the state of the current dialog (buttons showing, etc)
326 final Bundle savedState = new Bundle();
327 if (mCurrentDialog != null) {
328 mCurrentDialog.onSaveState(savedState);
329 }
330
Gus Prevasa7df7b22018-10-30 10:29:34 -0400331 if (mDialogShowing) {
332 mCurrentDialog.forceRemove();
Kevin Chyn02129b12018-11-01 16:47:12 -0700333 mDialogShowing = false;
Gus Prevasa7df7b22018-10-30 10:29:34 -0400334 }
Kevin Chyne1912712019-01-04 14:22:34 -0800335
Kevin Chyn02129b12018-11-01 16:47:12 -0700336 if (wasShowing) {
Kevin Chyne1912712019-01-04 14:22:34 -0800337 handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400338 }
339 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800340}