blob: 903e178b9107362fb2701e68022fb87b32189f72 [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:
69 handleShowDialog((SomeArgs) msg.obj);
70 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
181 private void handleShowDialog(SomeArgs args) {
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);
198 mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
Kevin Chyn42653e82018-01-19 14:15:46 -0800199 mDialogShowing = true;
200 }
201
Kevin Chyne9275662018-07-23 16:42:06 -0700202 private void handleBiometricAuthenticated() {
203 if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
204
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700205 mCurrentDialog.announceForAccessibility(
206 mContext.getResources()
207 .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
208 if (mCurrentDialog.requiresConfirmation()) {
209 mCurrentDialog.showConfirmationButton();
210 } else {
211 handleHideDialog(false /* userCanceled */);
212 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800213 }
214
Kevin Chyne9275662018-07-23 16:42:06 -0700215 private void handleBiometricHelp(String message) {
216 if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700217 mCurrentDialog.showHelpMessage(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800218 }
219
Kevin Chyne9275662018-07-23 16:42:06 -0700220 private void handleBiometricError(String error) {
221 if (DEBUG) Log.d(TAG, "handleBiometricError: " + error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800222 if (!mDialogShowing) {
223 if (DEBUG) Log.d(TAG, "Dialog already dismissed");
224 return;
225 }
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700226 mCurrentDialog.showErrorMessage(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800227 }
228
229 private void handleHideDialog(boolean userCanceled) {
Kevin Chyn87df0682018-04-10 19:29:23 -0700230 if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
Kevin Chyn42653e82018-01-19 14:15:46 -0800231 if (!mDialogShowing) {
232 // This can happen if there's a race and we get called from both
233 // onAuthenticated and onError, etc.
234 Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
235 return;
236 }
237 if (userCanceled) {
238 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700239 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
Kevin Chyn42653e82018-01-19 14:15:46 -0800240 } catch (RemoteException e) {
241 Log.e(TAG, "RemoteException when hiding dialog", e);
242 }
243 }
244 mReceiver = null;
Kevin Chyn42653e82018-01-19 14:15:46 -0800245 mDialogShowing = false;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700246 mCurrentDialog.startDismiss();
Kevin Chyn42653e82018-01-19 14:15:46 -0800247 }
248
249 private void handleButtonNegative() {
250 if (mReceiver == null) {
251 Log.e(TAG, "Receiver is null");
252 return;
253 }
254 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700255 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800256 } catch (RemoteException e) {
257 Log.e(TAG, "Remote exception when handling negative button", e);
258 }
259 handleHideDialog(false /* userCanceled */);
260 }
261
262 private void handleButtonPositive() {
263 if (mReceiver == null) {
264 Log.e(TAG, "Receiver is null");
265 return;
266 }
267 try {
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700268 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800269 } catch (RemoteException e) {
270 Log.e(TAG, "Remote exception when handling positive button", e);
271 }
272 handleHideDialog(false /* userCanceled */);
273 }
274
Kevin Chyn42653e82018-01-19 14:15:46 -0800275 private void handleUserCanceled() {
276 handleHideDialog(true /* userCanceled */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800277 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400278
279 @Override
280 protected void onConfigurationChanged(Configuration newConfig) {
281 if (mDialogShowing) {
282 mCurrentDialog.forceRemove();
283 }
284 createDialogs();
285 if (mDialogShowing) {
286 mCurrentDialog = mDialogs.get(mCurrentDialogArgs.argi1);
287 mCurrentDialog.forceRemove(); // Prevents intro animation when reattaching.
288 mDialogShowing = false;
289 handleShowDialog(mCurrentDialogArgs);
290 }
291 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800292}