blob: 74e170d0ad987a7864b2ba511d13cdc7600c2d2f [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 Chyn8429da22019-09-24 12:42:35 -070026import android.hardware.biometrics.BiometricConstants;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070027import android.hardware.biometrics.BiometricPrompt;
Kevin Chyn23289ef2018-11-28 16:32:36 -080028import android.hardware.biometrics.IBiometricServiceReceiverInternal;
Kevin Chynaae4a152018-01-18 11:48:09 -080029import android.os.Bundle;
Kevin Chyn050315f2019-08-08 14:22:54 -070030import android.os.Handler;
31import android.os.Looper;
Kevin Chyn42653e82018-01-19 14:15:46 -080032import android.os.RemoteException;
Kevin Chynaae4a152018-01-18 11:48:09 -080033import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080034import android.view.WindowManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080035
Kevin Chyn050315f2019-08-08 14:22:54 -070036import com.android.internal.annotations.VisibleForTesting;
Kevin Chyn42653e82018-01-19 14:15:46 -080037import com.android.internal.os.SomeArgs;
Kevin Chynaae4a152018-01-18 11:48:09 -080038import com.android.systemui.SystemUI;
39import com.android.systemui.statusbar.CommandQueue;
40
Kevin Chyn050315f2019-08-08 14:22:54 -070041import java.util.List;
42
Kevin Chyne9275662018-07-23 16:42:06 -070043/**
Kevin Chync53d9812019-07-30 18:10:30 -070044 * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
45 * appropriate biometric UI (e.g. BiometricDialogView).
Kevin Chyne9275662018-07-23 16:42:06 -070046 */
Kevin Chynf8688a02019-08-27 17:04:05 -070047public class AuthController extends SystemUI implements CommandQueue.Callbacks,
48 AuthDialogCallback {
Kevin Chynfc468262019-08-20 17:17:11 -070049
Kevin Chynf8688a02019-08-27 17:04:05 -070050 private static final String TAG = "BiometricPrompt/AuthController";
Kevin Chyn42653e82018-01-19 14:15:46 -080051 private static final boolean DEBUG = true;
52
Kevin Chyn050315f2019-08-08 14:22:54 -070053 private final Injector mInjector;
54
Kevin Chync53d9812019-07-30 18:10:30 -070055 // TODO: These should just be saved from onSaveState
Gus Prevasa7df7b22018-10-30 10:29:34 -040056 private SomeArgs mCurrentDialogArgs;
Kevin Chyn050315f2019-08-08 14:22:54 -070057 @VisibleForTesting
Kevin Chynf8688a02019-08-27 17:04:05 -070058 AuthDialog mCurrentDialog;
Kevin Chync53d9812019-07-30 18:10:30 -070059
Kevin Chyn050315f2019-08-08 14:22:54 -070060 private Handler mHandler = new Handler(Looper.getMainLooper());
Kevin Chyn42653e82018-01-19 14:15:46 -080061 private WindowManager mWindowManager;
Kevin Chyn050315f2019-08-08 14:22:54 -070062 @VisibleForTesting
63 IActivityTaskManager mActivityTaskManager;
64 @VisibleForTesting
65 BiometricTaskStackListener mTaskStackListener;
66 @VisibleForTesting
67 IBiometricServiceReceiverInternal mReceiver;
68
69 public class BiometricTaskStackListener extends TaskStackListener {
70 @Override
71 public void onTaskStackChanged() {
72 mHandler.post(mTaskStackChangedRunnable);
73 }
74 }
75
76 private final Runnable mTaskStackChangedRunnable = () -> {
77 if (mCurrentDialog != null) {
78 try {
79 final String clientPackage = mCurrentDialog.getOpPackageName();
80 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
81 final List<ActivityManager.RunningTaskInfo> runningTasks =
82 mActivityTaskManager.getTasks(1);
83 if (!runningTasks.isEmpty()) {
84 final String topPackage = runningTasks.get(0).topActivity.getPackageName();
85 if (!topPackage.contentEquals(clientPackage)) {
86 Log.w(TAG, "Evicting client due to: " + topPackage);
87 mCurrentDialog.dismissWithoutCallback(true /* animate */);
88 mCurrentDialog = null;
89 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
90 mReceiver = null;
91 }
92 }
93 } catch (RemoteException e) {
94 Log.e(TAG, "Remote exception", e);
95 }
96 }
97 };
Kevin Chyn42653e82018-01-19 14:15:46 -080098
Kevin Chync53d9812019-07-30 18:10:30 -070099 @Override
100 public void onTryAgainPressed() {
101 try {
102 mReceiver.onTryAgainPressed();
103 } catch (RemoteException e) {
104 Log.e(TAG, "RemoteException when handling try again", e);
Kevin Chyn5906c172018-07-23 15:43:02 -0700105 }
106 }
107
Kevin Chync53d9812019-07-30 18:10:30 -0700108 @Override
Kevin Chynff168dc2019-09-16 16:04:38 -0700109 public void onDeviceCredentialPressed() {
110 try {
111 mReceiver.onDeviceCredentialPressed();
112 } catch (RemoteException e) {
113 Log.e(TAG, "RemoteException when handling credential button", e);
114 }
115 }
116
117 @Override
Kevin Chyn050315f2019-08-08 14:22:54 -0700118 public void onDismissed(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700119 switch (reason) {
Kevin Chynf8688a02019-08-27 17:04:05 -0700120 case AuthDialogCallback.DISMISSED_USER_CANCELED:
Kevin Chync53d9812019-07-30 18:10:30 -0700121 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
122 break;
123
Kevin Chynf8688a02019-08-27 17:04:05 -0700124 case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
Kevin Chync53d9812019-07-30 18:10:30 -0700125 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
126 break;
127
Kevin Chynf8688a02019-08-27 17:04:05 -0700128 case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
Kevin Chynff168dc2019-09-16 16:04:38 -0700129 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
Kevin Chync53d9812019-07-30 18:10:30 -0700130 break;
131
Kevin Chynff168dc2019-09-16 16:04:38 -0700132 case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
133 sendResultAndCleanUp(
134 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700135 break;
136
Kevin Chynf8688a02019-08-27 17:04:05 -0700137 case AuthDialogCallback.DISMISSED_ERROR:
Kevin Chync53d9812019-07-30 18:10:30 -0700138 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
Kevin Chync53d9812019-07-30 18:10:30 -0700139 break;
Kevin Chyn050315f2019-08-08 14:22:54 -0700140
Kevin Chynf8688a02019-08-27 17:04:05 -0700141 case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
Kevin Chyn050315f2019-08-08 14:22:54 -0700142 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
143 break;
144
Kevin Chynff168dc2019-09-16 16:04:38 -0700145 case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
146 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
147 break;
148
Kevin Chync53d9812019-07-30 18:10:30 -0700149 default:
150 Log.e(TAG, "Unhandled reason: " + reason);
151 break;
Kevin Chync94b7db2019-05-15 17:28:16 -0700152 }
Kevin Chync53d9812019-07-30 18:10:30 -0700153 }
154
Kevin Chyn050315f2019-08-08 14:22:54 -0700155 private void sendResultAndCleanUp(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700156 if (mReceiver == null) {
157 Log.e(TAG, "Receiver is null");
158 return;
159 }
160 try {
Kevin Chyn050315f2019-08-08 14:22:54 -0700161 mReceiver.onDialogDismissed(reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700162 } catch (RemoteException e) {
163 Log.w(TAG, "Remote exception", e);
164 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700165 onDialogDismissed(reason);
166 }
167
168 public static class Injector {
169 IActivityTaskManager getActivityTaskManager() {
170 return ActivityTaskManager.getService();
171 }
172 }
173
Kevin Chynf8688a02019-08-27 17:04:05 -0700174 public AuthController() {
Kevin Chyn050315f2019-08-08 14:22:54 -0700175 this(new Injector());
176 }
177
178 @VisibleForTesting
Kevin Chynf8688a02019-08-27 17:04:05 -0700179 AuthController(Injector injector) {
Kevin Chyn050315f2019-08-08 14:22:54 -0700180 mInjector = injector;
Kevin Chync53d9812019-07-30 18:10:30 -0700181 }
Kevin Chync94b7db2019-05-15 17:28:16 -0700182
Kevin Chynaae4a152018-01-18 11:48:09 -0800183 @Override
184 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800185 final PackageManager pm = mContext.getPackageManager();
186 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
187 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
188 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500189 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400190 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700191 mActivityTaskManager = mInjector.getActivityTaskManager();
192
193 try {
194 mTaskStackListener = new BiometricTaskStackListener();
195 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
196 } catch (RemoteException e) {
197 Log.w(TAG, "Unable to register task stack listener", e);
198 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400199 }
200 }
201
Kevin Chynaae4a152018-01-18 11:48:09 -0800202 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800203 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
Kevin Chyn050315f2019-08-08 14:22:54 -0700204 int type, boolean requireConfirmation, int userId, String opPackageName) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800205 if (DEBUG) {
206 Log.d(TAG, "showBiometricDialog, type: " + type
207 + ", requireConfirmation: " + requireConfirmation);
208 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800209 SomeArgs args = SomeArgs.obtain();
210 args.arg1 = bundle;
211 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700212 args.argi1 = type;
213 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800214 args.argi2 = userId;
Kevin Chyn050315f2019-08-08 14:22:54 -0700215 args.arg4 = opPackageName;
Kevin Chync53d9812019-07-30 18:10:30 -0700216
217 boolean skipAnimation = false;
218 if (mCurrentDialog != null) {
219 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
220 skipAnimation = true;
221 }
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700222 showDialog(args, skipAnimation, null /* savedState */,
223 AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC);
Kevin Chynaae4a152018-01-18 11:48:09 -0800224 }
225
226 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700227 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
228 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
229 + " reason: " + failureReason);
230
Kevin Chyne1912712019-01-04 14:22:34 -0800231 if (authenticated) {
Kevin Chync53d9812019-07-30 18:10:30 -0700232 mCurrentDialog.onAuthenticationSucceeded();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700233 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700234 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700235 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800236 }
237
Kevin Chync53d9812019-07-30 18:10:30 -0700238 @Override
239 public void onBiometricHelp(String message) {
240 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
241
242 mCurrentDialog.onHelp(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800243 }
244
Kevin Chync53d9812019-07-30 18:10:30 -0700245 @Override
Kevin Chyn8429da22019-09-24 12:42:35 -0700246 public void onBiometricError(int errorCode, String error) {
247 if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
248
249 final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
250 || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
251 if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
252 mCurrentDialog.animateToCredentialUI();
253 } else {
254 mCurrentDialog.onError(error);
255 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800256 }
257
Kevin Chync53d9812019-07-30 18:10:30 -0700258 @Override
259 public void hideBiometricDialog() {
260 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
261
Kevin Chyn050315f2019-08-08 14:22:54 -0700262 mCurrentDialog.dismissFromSystemServer();
Kevin Chync53d9812019-07-30 18:10:30 -0700263 }
264
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700265 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
266 @AuthContainerView.Builder.InitialView int initialView) {
Kevin Chync53d9812019-07-30 18:10:30 -0700267 mCurrentDialogArgs = args;
268 final int type = args.argi1;
Kevin Chyn050315f2019-08-08 14:22:54 -0700269 final Bundle biometricPromptBundle = (Bundle) args.arg1;
270 final boolean requireConfirmation = (boolean) args.arg3;
271 final int userId = args.argi2;
272 final String opPackageName = (String) args.arg4;
Kevin Chync53d9812019-07-30 18:10:30 -0700273
274 // Create a new dialog but do not replace the current one yet.
Kevin Chynf8688a02019-08-27 17:04:05 -0700275 final AuthDialog newDialog = buildDialog(
Kevin Chyn050315f2019-08-08 14:22:54 -0700276 biometricPromptBundle,
277 requireConfirmation,
278 userId,
279 type,
Kevin Chynfc468262019-08-20 17:17:11 -0700280 opPackageName,
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700281 skipAnimation,
282 initialView);
Kevin Chync53d9812019-07-30 18:10:30 -0700283
284 if (newDialog == null) {
285 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800286 return;
287 }
Kevin Chync53d9812019-07-30 18:10:30 -0700288
289 if (DEBUG) {
290 Log.d(TAG, "showDialog, "
291 + " savedState: " + savedState
292 + " mCurrentDialog: " + mCurrentDialog
293 + " newDialog: " + newDialog
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700294 + " type: " + type
295 + " initialView: " + initialView);
Kevin Chync53d9812019-07-30 18:10:30 -0700296 }
297
Kevin Chyn9cf89912019-08-30 13:33:58 -0700298 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700299 // If somehow we're asked to show a dialog, the old one doesn't need to be animated
300 // away. This can happen if the app cancels and re-starts auth during configuration
301 // change. This is ugly because we also have to do things on onConfigurationChanged
302 // here.
303 mCurrentDialog.dismissWithoutCallback(false /* animate */);
304 }
305
306 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
307 mCurrentDialog = newDialog;
Kevin Chyn9cf89912019-08-30 13:33:58 -0700308 mCurrentDialog.show(mWindowManager, savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700309 }
310
Kevin Chyn050315f2019-08-08 14:22:54 -0700311 private void onDialogDismissed(@DismissedReason int reason) {
312 if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700313 if (mCurrentDialog == null) {
314 Log.w(TAG, "Dialog already dismissed");
Kevin Chyn42653e82018-01-19 14:15:46 -0800315 }
316 mReceiver = null;
Kevin Chync53d9812019-07-30 18:10:30 -0700317 mCurrentDialog = null;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800318 }
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);
Kevin Chyne1912712019-01-04 14:22:34 -0800323
324 // Save the state of the current dialog (buttons showing, etc)
Kevin Chyne1912712019-01-04 14:22:34 -0800325 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700326 final Bundle savedState = new Bundle();
Kevin Chyne1912712019-01-04 14:22:34 -0800327 mCurrentDialog.onSaveState(savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700328 mCurrentDialog.dismissWithoutCallback(false /* animate */);
329 mCurrentDialog = null;
Kevin Chyne1912712019-01-04 14:22:34 -0800330
Kevin Chyn27da7182019-09-11 12:17:55 -0700331 // Only show the dialog if necessary. If it was animating out, the dialog is supposed
332 // to send its pending callback immediately.
333 if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
334 != AuthContainerView.STATE_ANIMATING_OUT) {
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700335 final boolean credentialShowing =
336 savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
337
338 // We can assume if credential is showing, then biometric doesn't need to be shown,
339 // since credential is always after biometric.
340 final int initialView = credentialShowing
341 ? AuthContainerView.Builder.INITIAL_VIEW_CREDENTIAL
342 : AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC;
343
344 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, initialView);
Kevin Chyn27da7182019-09-11 12:17:55 -0700345 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400346 }
Kevin Chync53d9812019-07-30 18:10:30 -0700347 }
Kevin Chyne1912712019-01-04 14:22:34 -0800348
Kevin Chynf8688a02019-08-27 17:04:05 -0700349 protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700350 int userId, int type, String opPackageName, boolean skipIntro,
351 @AuthContainerView.Builder.InitialView int initialView) {
Kevin Chynd837ced2019-09-11 16:09:43 -0700352 return new AuthContainerView.Builder(mContext)
353 .setCallback(this)
354 .setBiometricPromptBundle(biometricPromptBundle)
355 .setRequireConfirmation(requireConfirmation)
356 .setUserId(userId)
357 .setOpPackageName(opPackageName)
358 .setSkipIntro(skipIntro)
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700359 .setInitialView(initialView)
Kevin Chynd837ced2019-09-11 16:09:43 -0700360 .build(type);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400361 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800362}