blob: 67bc8b62668914adf0e4ec22daf5397b999590fb [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;
24import android.hardware.biometrics.IBiometricPromptReceiver;
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 Chyn6cf54e82018-09-18 19:13:27 -070036import java.util.HashMap;
37import java.util.Map;
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 Chyn42653e82018-01-19 14:15:46 -080055
Kevin Chyn6cf54e82018-09-18 19:13:27 -070056 private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
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;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070060 private IBiometricPromptReceiver 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 Chyn42653e82018-01-19 14:15:46 -080063
64 private Handler mHandler = new Handler() {
65 @Override
66 public void handleMessage(Message msg) {
67 switch(msg.what) {
68 case MSG_SHOW_DIALOG:
Kevin Chyn02129b12018-11-01 16:47:12 -070069 handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */);
Kevin Chyn42653e82018-01-19 14:15:46 -080070 break;
Kevin Chyne9275662018-07-23 16:42:06 -070071 case MSG_BIOMETRIC_AUTHENTICATED:
72 handleBiometricAuthenticated();
Kevin Chyn42653e82018-01-19 14:15:46 -080073 break;
Kevin Chyne9275662018-07-23 16:42:06 -070074 case MSG_BIOMETRIC_HELP:
75 handleBiometricHelp((String) msg.obj);
Kevin Chyn42653e82018-01-19 14:15:46 -080076 break;
Kevin Chyne9275662018-07-23 16:42:06 -070077 case MSG_BIOMETRIC_ERROR:
78 handleBiometricError((String) msg.obj);
Kevin Chyn42653e82018-01-19 14:15:46 -080079 break;
80 case MSG_HIDE_DIALOG:
81 handleHideDialog((Boolean) msg.obj);
82 break;
83 case MSG_BUTTON_NEGATIVE:
84 handleButtonNegative();
85 break;
86 case MSG_USER_CANCELED:
87 handleUserCanceled();
88 break;
89 case MSG_BUTTON_POSITIVE:
90 handleButtonPositive();
91 break;
Kevin Chyn42653e82018-01-19 14:15:46 -080092 }
93 }
94 };
Kevin Chynaae4a152018-01-18 11:48:09 -080095
Kevin Chyn5906c172018-07-23 15:43:02 -070096 private class Callback implements DialogViewCallback {
97 @Override
98 public void onUserCanceled() {
Kevin Chyne9275662018-07-23 16:42:06 -070099 mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700100 }
101
102 @Override
103 public void onErrorShown() {
104 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_DIALOG,
105 false /* userCanceled */), BiometricPrompt.HIDE_DIALOG_DELAY);
106 }
107
108 @Override
109 public void onNegativePressed() {
Kevin Chyne9275662018-07-23 16:42:06 -0700110 mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700111 }
112
113 @Override
114 public void onPositivePressed() {
Kevin Chyne9275662018-07-23 16:42:06 -0700115 mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
Kevin Chyn5906c172018-07-23 15:43:02 -0700116 }
117 }
118
Kevin Chynaae4a152018-01-18 11:48:09 -0800119 @Override
120 public void start() {
Gus Prevasa7df7b22018-10-30 10:29:34 -0400121 createDialogs();
122
123 if (!mDialogs.isEmpty()) {
124 getComponent(CommandQueue.class).addCallbacks(this);
125 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
126 }
127 }
128
129 private void createDialogs() {
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700130 final PackageManager pm = mContext.getPackageManager();
131 mDialogs = new HashMap<>();
132 if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
133 mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback));
Kevin Chynaae4a152018-01-18 11:48:09 -0800134 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700135 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
136 mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT,
137 new FingerprintDialogView(mContext, mCallback));
138 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800139 }
140
141 @Override
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700142 public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
143 boolean requireConfirmation) {
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -0700144 if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800145 // Remove these messages as they are part of the previous client
Kevin Chyne9275662018-07-23 16:42:06 -0700146 mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
147 mHandler.removeMessages(MSG_BIOMETRIC_HELP);
148 mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
Kevin Chyn42653e82018-01-19 14:15:46 -0800149 SomeArgs args = SomeArgs.obtain();
150 args.arg1 = bundle;
151 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700152 args.argi1 = type;
153 args.arg3 = requireConfirmation;
Kevin Chyn42653e82018-01-19 14:15:46 -0800154 mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800155 }
156
157 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700158 public void onBiometricAuthenticated() {
159 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated");
160 mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800161 }
162
163 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700164 public void onBiometricHelp(String message) {
165 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
166 mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800167 }
168
169 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700170 public void onBiometricError(String error) {
171 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
172 mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget();
Kevin Chynaae4a152018-01-18 11:48:09 -0800173 }
174
175 @Override
Kevin Chyne9275662018-07-23 16:42:06 -0700176 public void hideBiometricDialog() {
177 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
Kevin Chyn42653e82018-01-19 14:15:46 -0800178 mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
179 }
180
Kevin Chyn02129b12018-11-01 16:47:12 -0700181 private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
Gus Prevasa7df7b22018-10-30 10:29:34 -0400182 mCurrentDialogArgs = args;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700183 final int type = args.argi1;
184 mCurrentDialog = mDialogs.get(type);
185
Kevin Chyn87df0682018-04-10 19:29:23 -0700186 if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700187 + mCurrentDialog.isAnimatingAway() + " type: " + type);
188
189 if (mCurrentDialog.isAnimatingAway()) {
190 mCurrentDialog.forceRemove();
Kevin Chyn87df0682018-04-10 19:29:23 -0700191 } else if (mDialogShowing) {
Kevin Chyn42653e82018-01-19 14:15:46 -0800192 Log.w(TAG, "Dialog already showing");
193 return;
194 }
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700195 mReceiver = (IBiometricPromptReceiver) args.arg2;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700196 mCurrentDialog.setBundle((Bundle)args.arg1);
197 mCurrentDialog.setRequireConfirmation((boolean)args.arg3);
Kevin Chyn02129b12018-11-01 16:47:12 -0700198 mCurrentDialog.setSkipIntro(skipAnimation);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700199 mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
Kevin Chyn42653e82018-01-19 14:15:46 -0800200 mDialogShowing = true;
201 }
202
Kevin Chyne9275662018-07-23 16:42:06 -0700203 private void handleBiometricAuthenticated() {
204 if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
205
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700206 mCurrentDialog.announceForAccessibility(
207 mContext.getResources()
208 .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
209 if (mCurrentDialog.requiresConfirmation()) {
210 mCurrentDialog.showConfirmationButton();
211 } else {
212 handleHideDialog(false /* userCanceled */);
213 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800214 }
215
Kevin Chyne9275662018-07-23 16:42:06 -0700216 private void handleBiometricHelp(String message) {
217 if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700218 mCurrentDialog.showHelpMessage(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800219 }
220
Kevin Chyne9275662018-07-23 16:42:06 -0700221 private void handleBiometricError(String error) {
222 if (DEBUG) Log.d(TAG, "handleBiometricError: " + error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800223 if (!mDialogShowing) {
224 if (DEBUG) Log.d(TAG, "Dialog already dismissed");
225 return;
226 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700227 mCurrentDialog.showErrorMessage(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800228 }
229
230 private void handleHideDialog(boolean userCanceled) {
Kevin Chyn87df0682018-04-10 19:29:23 -0700231 if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
Kevin Chyn42653e82018-01-19 14:15:46 -0800232 if (!mDialogShowing) {
233 // This can happen if there's a race and we get called from both
234 // onAuthenticated and onError, etc.
235 Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
236 return;
237 }
238 if (userCanceled) {
239 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700240 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
Kevin Chyn42653e82018-01-19 14:15:46 -0800241 } catch (RemoteException e) {
242 Log.e(TAG, "RemoteException when hiding dialog", e);
243 }
244 }
245 mReceiver = null;
Kevin Chyn42653e82018-01-19 14:15:46 -0800246 mDialogShowing = false;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700247 mCurrentDialog.startDismiss();
Kevin Chyn42653e82018-01-19 14:15:46 -0800248 }
249
250 private void handleButtonNegative() {
251 if (mReceiver == null) {
252 Log.e(TAG, "Receiver is null");
253 return;
254 }
255 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700256 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800257 } catch (RemoteException e) {
258 Log.e(TAG, "Remote exception when handling negative button", e);
259 }
260 handleHideDialog(false /* userCanceled */);
261 }
262
263 private void handleButtonPositive() {
264 if (mReceiver == null) {
265 Log.e(TAG, "Receiver is null");
266 return;
267 }
268 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700269 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800270 } catch (RemoteException e) {
271 Log.e(TAG, "Remote exception when handling positive button", e);
272 }
273 handleHideDialog(false /* userCanceled */);
274 }
275
Kevin Chyn42653e82018-01-19 14:15:46 -0800276 private void handleUserCanceled() {
277 handleHideDialog(true /* userCanceled */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800278 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400279
280 @Override
281 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700282 super.onConfigurationChanged(newConfig);
283 final boolean wasShowing = mDialogShowing;
Gus Prevasa7df7b22018-10-30 10:29:34 -0400284 if (mDialogShowing) {
285 mCurrentDialog.forceRemove();
Kevin Chyn02129b12018-11-01 16:47:12 -0700286 mDialogShowing = false;
Gus Prevasa7df7b22018-10-30 10:29:34 -0400287 }
288 createDialogs();
Kevin Chyn02129b12018-11-01 16:47:12 -0700289 if (wasShowing) {
290 handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400291 }
292 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800293}