blob: cdc2623d34fd3a6481edb5158c5419657e76041c [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
Kevin Chynf8688a02019-08-27 17:04:05 -070014 * limitations under the License.
Kevin Chynaae4a152018-01-18 11:48:09 -080015 */
16
Kevin Chyne9275662018-07-23 16:42:06 -070017package com.android.systemui.biometrics;
Kevin Chynaae4a152018-01-18 11:48:09 -080018
Kevin Chyn050315f2019-08-08 14:22:54 -070019import android.app.ActivityManager;
20import android.app.ActivityTaskManager;
21import android.app.IActivityTaskManager;
22import android.app.TaskStackListener;
Kevin Chyn42653e82018-01-19 14:15:46 -080023import android.content.Context;
Kevin Chynaae4a152018-01-18 11:48:09 -080024import android.content.pm.PackageManager;
Gus Prevasa7df7b22018-10-30 10:29:34 -040025import android.content.res.Configuration;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070026import android.hardware.biometrics.Authenticator;
Kevin Chyn8429da22019-09-24 12:42:35 -070027import android.hardware.biometrics.BiometricConstants;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070028import android.hardware.biometrics.BiometricPrompt;
Kevin Chyn23289ef2018-11-28 16:32:36 -080029import android.hardware.biometrics.IBiometricServiceReceiverInternal;
Kevin Chynaae4a152018-01-18 11:48:09 -080030import android.os.Bundle;
Kevin Chyn050315f2019-08-08 14:22:54 -070031import android.os.Handler;
32import android.os.Looper;
Kevin Chyn42653e82018-01-19 14:15:46 -080033import android.os.RemoteException;
Kevin Chynaae4a152018-01-18 11:48:09 -080034import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080035import android.view.WindowManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080036
Kevin Chyn050315f2019-08-08 14:22:54 -070037import com.android.internal.annotations.VisibleForTesting;
Kevin Chyn42653e82018-01-19 14:15:46 -080038import com.android.internal.os.SomeArgs;
Kevin Chynaae4a152018-01-18 11:48:09 -080039import com.android.systemui.SystemUI;
40import com.android.systemui.statusbar.CommandQueue;
41
Kevin Chyn050315f2019-08-08 14:22:54 -070042import java.util.List;
43
Kevin Chyne9275662018-07-23 16:42:06 -070044/**
Kevin Chync53d9812019-07-30 18:10:30 -070045 * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
46 * appropriate biometric UI (e.g. BiometricDialogView).
Kevin Chyne9275662018-07-23 16:42:06 -070047 */
Kevin Chynf8688a02019-08-27 17:04:05 -070048public class AuthController extends SystemUI implements CommandQueue.Callbacks,
49 AuthDialogCallback {
Kevin Chynfc468262019-08-20 17:17:11 -070050
Kevin Chynf8688a02019-08-27 17:04:05 -070051 private static final String TAG = "BiometricPrompt/AuthController";
Kevin Chyn42653e82018-01-19 14:15:46 -080052 private static final boolean DEBUG = true;
53
Kevin Chyn050315f2019-08-08 14:22:54 -070054 private final Injector mInjector;
55
Kevin Chync53d9812019-07-30 18:10:30 -070056 // TODO: These should just be saved from onSaveState
Gus Prevasa7df7b22018-10-30 10:29:34 -040057 private SomeArgs mCurrentDialogArgs;
Kevin Chyn050315f2019-08-08 14:22:54 -070058 @VisibleForTesting
Kevin Chynf8688a02019-08-27 17:04:05 -070059 AuthDialog mCurrentDialog;
Kevin Chync53d9812019-07-30 18:10:30 -070060
Kevin Chyn050315f2019-08-08 14:22:54 -070061 private Handler mHandler = new Handler(Looper.getMainLooper());
Kevin Chyn42653e82018-01-19 14:15:46 -080062 private WindowManager mWindowManager;
Kevin Chyn050315f2019-08-08 14:22:54 -070063 @VisibleForTesting
64 IActivityTaskManager mActivityTaskManager;
65 @VisibleForTesting
66 BiometricTaskStackListener mTaskStackListener;
67 @VisibleForTesting
68 IBiometricServiceReceiverInternal mReceiver;
69
70 public class BiometricTaskStackListener extends TaskStackListener {
71 @Override
72 public void onTaskStackChanged() {
73 mHandler.post(mTaskStackChangedRunnable);
74 }
75 }
76
77 private final Runnable mTaskStackChangedRunnable = () -> {
78 if (mCurrentDialog != null) {
79 try {
80 final String clientPackage = mCurrentDialog.getOpPackageName();
81 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
82 final List<ActivityManager.RunningTaskInfo> runningTasks =
83 mActivityTaskManager.getTasks(1);
84 if (!runningTasks.isEmpty()) {
85 final String topPackage = runningTasks.get(0).topActivity.getPackageName();
86 if (!topPackage.contentEquals(clientPackage)) {
87 Log.w(TAG, "Evicting client due to: " + topPackage);
88 mCurrentDialog.dismissWithoutCallback(true /* animate */);
89 mCurrentDialog = null;
90 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
91 mReceiver = null;
92 }
93 }
94 } catch (RemoteException e) {
95 Log.e(TAG, "Remote exception", e);
96 }
97 }
98 };
Kevin Chyn42653e82018-01-19 14:15:46 -080099
Kevin Chync53d9812019-07-30 18:10:30 -0700100 @Override
101 public void onTryAgainPressed() {
102 try {
103 mReceiver.onTryAgainPressed();
104 } catch (RemoteException e) {
105 Log.e(TAG, "RemoteException when handling try again", e);
Kevin Chyn5906c172018-07-23 15:43:02 -0700106 }
107 }
108
Kevin Chync53d9812019-07-30 18:10:30 -0700109 @Override
Kevin Chynff168dc2019-09-16 16:04:38 -0700110 public void onDeviceCredentialPressed() {
111 try {
112 mReceiver.onDeviceCredentialPressed();
113 } catch (RemoteException e) {
114 Log.e(TAG, "RemoteException when handling credential button", e);
115 }
116 }
117
118 @Override
Kevin Chyn050315f2019-08-08 14:22:54 -0700119 public void onDismissed(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700120 switch (reason) {
Kevin Chynf8688a02019-08-27 17:04:05 -0700121 case AuthDialogCallback.DISMISSED_USER_CANCELED:
Kevin Chync53d9812019-07-30 18:10:30 -0700122 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
123 break;
124
Kevin Chynf8688a02019-08-27 17:04:05 -0700125 case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
Kevin Chync53d9812019-07-30 18:10:30 -0700126 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
127 break;
128
Kevin Chynf8688a02019-08-27 17:04:05 -0700129 case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
Kevin Chynff168dc2019-09-16 16:04:38 -0700130 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
Kevin Chync53d9812019-07-30 18:10:30 -0700131 break;
132
Kevin Chynff168dc2019-09-16 16:04:38 -0700133 case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
134 sendResultAndCleanUp(
135 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700136 break;
137
Kevin Chynf8688a02019-08-27 17:04:05 -0700138 case AuthDialogCallback.DISMISSED_ERROR:
Kevin Chync53d9812019-07-30 18:10:30 -0700139 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
Kevin Chync53d9812019-07-30 18:10:30 -0700140 break;
Kevin Chyn050315f2019-08-08 14:22:54 -0700141
Kevin Chynf8688a02019-08-27 17:04:05 -0700142 case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
Kevin Chyn050315f2019-08-08 14:22:54 -0700143 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
144 break;
145
Kevin Chynff168dc2019-09-16 16:04:38 -0700146 case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
147 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
148 break;
149
Kevin Chync53d9812019-07-30 18:10:30 -0700150 default:
151 Log.e(TAG, "Unhandled reason: " + reason);
152 break;
Kevin Chync94b7db2019-05-15 17:28:16 -0700153 }
Kevin Chync53d9812019-07-30 18:10:30 -0700154 }
155
Kevin Chyn050315f2019-08-08 14:22:54 -0700156 private void sendResultAndCleanUp(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700157 if (mReceiver == null) {
158 Log.e(TAG, "Receiver is null");
159 return;
160 }
161 try {
Kevin Chyn050315f2019-08-08 14:22:54 -0700162 mReceiver.onDialogDismissed(reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700163 } catch (RemoteException e) {
164 Log.w(TAG, "Remote exception", e);
165 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700166 onDialogDismissed(reason);
167 }
168
169 public static class Injector {
170 IActivityTaskManager getActivityTaskManager() {
171 return ActivityTaskManager.getService();
172 }
173 }
174
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400175 public AuthController(Context context) {
176 this(context, new Injector());
Kevin Chyn050315f2019-08-08 14:22:54 -0700177 }
178
179 @VisibleForTesting
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400180 AuthController(Context context, Injector injector) {
181 super(context);
Kevin Chyn050315f2019-08-08 14:22:54 -0700182 mInjector = injector;
Kevin Chync53d9812019-07-30 18:10:30 -0700183 }
Kevin Chync94b7db2019-05-15 17:28:16 -0700184
Kevin Chynaae4a152018-01-18 11:48:09 -0800185 @Override
186 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800187 final PackageManager pm = mContext.getPackageManager();
188 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
189 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
190 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500191 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400192 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700193 mActivityTaskManager = mInjector.getActivityTaskManager();
194
195 try {
196 mTaskStackListener = new BiometricTaskStackListener();
197 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
198 } catch (RemoteException e) {
199 Log.w(TAG, "Unable to register task stack listener", e);
200 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400201 }
202 }
203
Kevin Chynaae4a152018-01-18 11:48:09 -0800204 @Override
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700205 public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
206 int biometricModality, boolean requireConfirmation, int userId, String opPackageName) {
207 final int authenticators = Utils.getAuthenticators(bundle);
208
Kevin Chyn158fefb2019-01-03 18:59:05 -0800209 if (DEBUG) {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700210 Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
211 + ", biometricModality: " + biometricModality
Kevin Chyn158fefb2019-01-03 18:59:05 -0800212 + ", requireConfirmation: " + requireConfirmation);
213 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800214 SomeArgs args = SomeArgs.obtain();
215 args.arg1 = bundle;
216 args.arg2 = receiver;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700217 args.argi1 = biometricModality;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700218 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800219 args.argi2 = userId;
Kevin Chyn050315f2019-08-08 14:22:54 -0700220 args.arg4 = opPackageName;
Kevin Chync53d9812019-07-30 18:10:30 -0700221
222 boolean skipAnimation = false;
223 if (mCurrentDialog != null) {
224 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
225 skipAnimation = true;
226 }
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700227
228 showDialog(args, skipAnimation, null /* savedState */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800229 }
230
231 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700232 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
233 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
234 + " reason: " + failureReason);
235
Kevin Chyne1912712019-01-04 14:22:34 -0800236 if (authenticated) {
Kevin Chync53d9812019-07-30 18:10:30 -0700237 mCurrentDialog.onAuthenticationSucceeded();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700238 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700239 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700240 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800241 }
242
Kevin Chync53d9812019-07-30 18:10:30 -0700243 @Override
244 public void onBiometricHelp(String message) {
245 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
246
247 mCurrentDialog.onHelp(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800248 }
249
Kevin Chync53d9812019-07-30 18:10:30 -0700250 @Override
Kevin Chyn8429da22019-09-24 12:42:35 -0700251 public void onBiometricError(int errorCode, String error) {
252 if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
253
254 final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
255 || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
256 if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
257 mCurrentDialog.animateToCredentialUI();
258 } else {
259 mCurrentDialog.onError(error);
260 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800261 }
262
Kevin Chync53d9812019-07-30 18:10:30 -0700263 @Override
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700264 public void hideAuthenticationDialog() {
265 if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
Kevin Chync53d9812019-07-30 18:10:30 -0700266
Kevin Chyn050315f2019-08-08 14:22:54 -0700267 mCurrentDialog.dismissFromSystemServer();
Kevin Chync53d9812019-07-30 18:10:30 -0700268 }
269
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700270 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
Kevin Chync53d9812019-07-30 18:10:30 -0700271 mCurrentDialogArgs = args;
272 final int type = args.argi1;
Kevin Chyn050315f2019-08-08 14:22:54 -0700273 final Bundle biometricPromptBundle = (Bundle) args.arg1;
274 final boolean requireConfirmation = (boolean) args.arg3;
275 final int userId = args.argi2;
276 final String opPackageName = (String) args.arg4;
Kevin Chync53d9812019-07-30 18:10:30 -0700277
278 // Create a new dialog but do not replace the current one yet.
Kevin Chynf8688a02019-08-27 17:04:05 -0700279 final AuthDialog newDialog = buildDialog(
Kevin Chyn050315f2019-08-08 14:22:54 -0700280 biometricPromptBundle,
281 requireConfirmation,
282 userId,
283 type,
Kevin Chynfc468262019-08-20 17:17:11 -0700284 opPackageName,
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700285 skipAnimation);
Kevin Chync53d9812019-07-30 18:10:30 -0700286
287 if (newDialog == null) {
288 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800289 return;
290 }
Kevin Chync53d9812019-07-30 18:10:30 -0700291
292 if (DEBUG) {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700293 Log.d(TAG, "showDialog: " + args
Kevin Chync53d9812019-07-30 18:10:30 -0700294 + " savedState: " + savedState
295 + " mCurrentDialog: " + mCurrentDialog
296 + " newDialog: " + newDialog
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700297 + " type: " + type);
Kevin Chync53d9812019-07-30 18:10:30 -0700298 }
299
Kevin Chyn9cf89912019-08-30 13:33:58 -0700300 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700301 // If somehow we're asked to show a dialog, the old one doesn't need to be animated
302 // away. This can happen if the app cancels and re-starts auth during configuration
303 // change. This is ugly because we also have to do things on onConfigurationChanged
304 // here.
305 mCurrentDialog.dismissWithoutCallback(false /* animate */);
306 }
307
308 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
309 mCurrentDialog = newDialog;
Kevin Chyn9cf89912019-08-30 13:33:58 -0700310 mCurrentDialog.show(mWindowManager, savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700311 }
312
Kevin Chyn050315f2019-08-08 14:22:54 -0700313 private void onDialogDismissed(@DismissedReason int reason) {
314 if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700315 if (mCurrentDialog == null) {
316 Log.w(TAG, "Dialog already dismissed");
Kevin Chyn42653e82018-01-19 14:15:46 -0800317 }
318 mReceiver = null;
Kevin Chync53d9812019-07-30 18:10:30 -0700319 mCurrentDialog = null;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800320 }
321
Gus Prevasa7df7b22018-10-30 10:29:34 -0400322 @Override
323 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700324 super.onConfigurationChanged(newConfig);
Kevin Chyne1912712019-01-04 14:22:34 -0800325
326 // Save the state of the current dialog (buttons showing, etc)
Kevin Chyne1912712019-01-04 14:22:34 -0800327 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700328 final Bundle savedState = new Bundle();
Kevin Chyne1912712019-01-04 14:22:34 -0800329 mCurrentDialog.onSaveState(savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700330 mCurrentDialog.dismissWithoutCallback(false /* animate */);
331 mCurrentDialog = null;
Kevin Chyne1912712019-01-04 14:22:34 -0800332
Kevin Chyn27da7182019-09-11 12:17:55 -0700333 // Only show the dialog if necessary. If it was animating out, the dialog is supposed
334 // to send its pending callback immediately.
335 if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
336 != AuthContainerView.STATE_ANIMATING_OUT) {
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700337 final boolean credentialShowing =
338 savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700339 if (credentialShowing) {
340 // TODO: Clean this up
341 Bundle bundle = (Bundle) mCurrentDialogArgs.arg1;
342 bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
343 Authenticator.TYPE_CREDENTIAL);
344 }
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700345
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700346 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
Kevin Chyn27da7182019-09-11 12:17:55 -0700347 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400348 }
Kevin Chync53d9812019-07-30 18:10:30 -0700349 }
Kevin Chyne1912712019-01-04 14:22:34 -0800350
Kevin Chynf8688a02019-08-27 17:04:05 -0700351 protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700352 int userId, int type, String opPackageName, boolean skipIntro) {
Kevin Chynd837ced2019-09-11 16:09:43 -0700353 return new AuthContainerView.Builder(mContext)
354 .setCallback(this)
355 .setBiometricPromptBundle(biometricPromptBundle)
356 .setRequireConfirmation(requireConfirmation)
357 .setUserId(userId)
358 .setOpPackageName(opPackageName)
359 .setSkipIntro(skipIntro)
360 .build(type);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400361 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800362}