blob: 85f39ce9d50c7f1964ae56528eeb7581a8137102 [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;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070026import android.hardware.biometrics.BiometricPrompt;
Kevin Chyn23289ef2018-11-28 16:32:36 -080027import android.hardware.biometrics.IBiometricServiceReceiverInternal;
Kevin Chynaae4a152018-01-18 11:48:09 -080028import android.os.Bundle;
Kevin Chyn050315f2019-08-08 14:22:54 -070029import android.os.Handler;
30import android.os.Looper;
Kevin Chyn42653e82018-01-19 14:15:46 -080031import android.os.RemoteException;
Kevin Chynfc468262019-08-20 17:17:11 -070032import android.provider.Settings;
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 private static final String USE_NEW_DIALOG =
Kevin Chynf8688a02019-08-27 17:04:05 -070050 "com.android.systemui.biometrics.AuthController.USE_NEW_DIALOG";
Kevin Chynfc468262019-08-20 17:17:11 -070051
Kevin Chynf8688a02019-08-27 17:04:05 -070052 private static final String TAG = "BiometricPrompt/AuthController";
Kevin Chyn42653e82018-01-19 14:15:46 -080053 private static final boolean DEBUG = true;
54
Kevin Chyn050315f2019-08-08 14:22:54 -070055 private final Injector mInjector;
56
Kevin Chync53d9812019-07-30 18:10:30 -070057 // TODO: These should just be saved from onSaveState
Gus Prevasa7df7b22018-10-30 10:29:34 -040058 private SomeArgs mCurrentDialogArgs;
Kevin Chyn050315f2019-08-08 14:22:54 -070059 @VisibleForTesting
Kevin Chynf8688a02019-08-27 17:04:05 -070060 AuthDialog mCurrentDialog;
Kevin Chync53d9812019-07-30 18:10:30 -070061
Kevin Chyn050315f2019-08-08 14:22:54 -070062 private Handler mHandler = new Handler(Looper.getMainLooper());
Kevin Chyn42653e82018-01-19 14:15:46 -080063 private WindowManager mWindowManager;
Kevin Chyn050315f2019-08-08 14:22:54 -070064 @VisibleForTesting
65 IActivityTaskManager mActivityTaskManager;
66 @VisibleForTesting
67 BiometricTaskStackListener mTaskStackListener;
68 @VisibleForTesting
69 IBiometricServiceReceiverInternal mReceiver;
70
71 public class BiometricTaskStackListener extends TaskStackListener {
72 @Override
73 public void onTaskStackChanged() {
74 mHandler.post(mTaskStackChangedRunnable);
75 }
76 }
77
78 private final Runnable mTaskStackChangedRunnable = () -> {
79 if (mCurrentDialog != null) {
80 try {
81 final String clientPackage = mCurrentDialog.getOpPackageName();
82 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
83 final List<ActivityManager.RunningTaskInfo> runningTasks =
84 mActivityTaskManager.getTasks(1);
85 if (!runningTasks.isEmpty()) {
86 final String topPackage = runningTasks.get(0).topActivity.getPackageName();
87 if (!topPackage.contentEquals(clientPackage)) {
88 Log.w(TAG, "Evicting client due to: " + topPackage);
89 mCurrentDialog.dismissWithoutCallback(true /* animate */);
90 mCurrentDialog = null;
91 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
92 mReceiver = null;
93 }
94 }
95 } catch (RemoteException e) {
96 Log.e(TAG, "Remote exception", e);
97 }
98 }
99 };
Kevin Chyn42653e82018-01-19 14:15:46 -0800100
Kevin Chync53d9812019-07-30 18:10:30 -0700101 @Override
102 public void onTryAgainPressed() {
103 try {
104 mReceiver.onTryAgainPressed();
105 } catch (RemoteException e) {
106 Log.e(TAG, "RemoteException when handling try again", e);
Kevin Chyn5906c172018-07-23 15:43:02 -0700107 }
108 }
109
Kevin Chync53d9812019-07-30 18:10:30 -0700110 @Override
Kevin Chyn050315f2019-08-08 14:22:54 -0700111 public void onDismissed(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700112 switch (reason) {
Kevin Chynf8688a02019-08-27 17:04:05 -0700113 case AuthDialogCallback.DISMISSED_USER_CANCELED:
Kevin Chync53d9812019-07-30 18:10:30 -0700114 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
115 break;
116
Kevin Chynf8688a02019-08-27 17:04:05 -0700117 case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
Kevin Chync53d9812019-07-30 18:10:30 -0700118 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
119 break;
120
Kevin Chynf8688a02019-08-27 17:04:05 -0700121 case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
Kevin Chync53d9812019-07-30 18:10:30 -0700122 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
123 break;
124
Kevin Chynf8688a02019-08-27 17:04:05 -0700125 case AuthDialogCallback.DISMISSED_AUTHENTICATED:
Kevin Chync53d9812019-07-30 18:10:30 -0700126 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700127 break;
128
Kevin Chynf8688a02019-08-27 17:04:05 -0700129 case AuthDialogCallback.DISMISSED_ERROR:
Kevin Chync53d9812019-07-30 18:10:30 -0700130 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
Kevin Chync53d9812019-07-30 18:10:30 -0700131 break;
Kevin Chyn050315f2019-08-08 14:22:54 -0700132
Kevin Chynf8688a02019-08-27 17:04:05 -0700133 case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
Kevin Chyn050315f2019-08-08 14:22:54 -0700134 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
135 break;
136
Kevin Chync53d9812019-07-30 18:10:30 -0700137 default:
138 Log.e(TAG, "Unhandled reason: " + reason);
139 break;
Kevin Chync94b7db2019-05-15 17:28:16 -0700140 }
Kevin Chync53d9812019-07-30 18:10:30 -0700141 }
142
Kevin Chyn050315f2019-08-08 14:22:54 -0700143 private void sendResultAndCleanUp(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700144 if (mReceiver == null) {
145 Log.e(TAG, "Receiver is null");
146 return;
147 }
148 try {
Kevin Chyn050315f2019-08-08 14:22:54 -0700149 mReceiver.onDialogDismissed(reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700150 } catch (RemoteException e) {
151 Log.w(TAG, "Remote exception", e);
152 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700153 onDialogDismissed(reason);
154 }
155
156 public static class Injector {
157 IActivityTaskManager getActivityTaskManager() {
158 return ActivityTaskManager.getService();
159 }
160 }
161
Kevin Chynf8688a02019-08-27 17:04:05 -0700162 public AuthController() {
Kevin Chyn050315f2019-08-08 14:22:54 -0700163 this(new Injector());
164 }
165
166 @VisibleForTesting
Kevin Chynf8688a02019-08-27 17:04:05 -0700167 AuthController(Injector injector) {
Kevin Chyn050315f2019-08-08 14:22:54 -0700168 mInjector = injector;
Kevin Chync53d9812019-07-30 18:10:30 -0700169 }
Kevin Chync94b7db2019-05-15 17:28:16 -0700170
Kevin Chynaae4a152018-01-18 11:48:09 -0800171 @Override
172 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800173 final PackageManager pm = mContext.getPackageManager();
174 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
175 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
176 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500177 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400178 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700179 mActivityTaskManager = mInjector.getActivityTaskManager();
180
181 try {
182 mTaskStackListener = new BiometricTaskStackListener();
183 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
184 } catch (RemoteException e) {
185 Log.w(TAG, "Unable to register task stack listener", e);
186 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400187 }
188 }
189
Kevin Chynaae4a152018-01-18 11:48:09 -0800190 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800191 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
Kevin Chyn050315f2019-08-08 14:22:54 -0700192 int type, boolean requireConfirmation, int userId, String opPackageName) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800193 if (DEBUG) {
194 Log.d(TAG, "showBiometricDialog, type: " + type
195 + ", requireConfirmation: " + requireConfirmation);
196 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800197 SomeArgs args = SomeArgs.obtain();
198 args.arg1 = bundle;
199 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700200 args.argi1 = type;
201 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800202 args.argi2 = userId;
Kevin Chyn050315f2019-08-08 14:22:54 -0700203 args.arg4 = opPackageName;
Kevin Chync53d9812019-07-30 18:10:30 -0700204
205 boolean skipAnimation = false;
206 if (mCurrentDialog != null) {
207 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
208 skipAnimation = true;
209 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700210 showDialog(args, skipAnimation, null /* savedState */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800211 }
212
213 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700214 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
215 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
216 + " reason: " + failureReason);
217
Kevin Chyne1912712019-01-04 14:22:34 -0800218 if (authenticated) {
Kevin Chync53d9812019-07-30 18:10:30 -0700219 mCurrentDialog.onAuthenticationSucceeded();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700220 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700221 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700222 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800223 }
224
Kevin Chync53d9812019-07-30 18:10:30 -0700225 @Override
226 public void onBiometricHelp(String message) {
227 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
228
229 mCurrentDialog.onHelp(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800230 }
231
Kevin Chync53d9812019-07-30 18:10:30 -0700232 @Override
233 public void onBiometricError(String error) {
234 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
235 mCurrentDialog.onError(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800236 }
237
Kevin Chync53d9812019-07-30 18:10:30 -0700238 @Override
239 public void hideBiometricDialog() {
240 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
241
Kevin Chyn050315f2019-08-08 14:22:54 -0700242 mCurrentDialog.dismissFromSystemServer();
Kevin Chync53d9812019-07-30 18:10:30 -0700243 }
244
245 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
246 mCurrentDialogArgs = args;
247 final int type = args.argi1;
Kevin Chyn050315f2019-08-08 14:22:54 -0700248 final Bundle biometricPromptBundle = (Bundle) args.arg1;
249 final boolean requireConfirmation = (boolean) args.arg3;
250 final int userId = args.argi2;
251 final String opPackageName = (String) args.arg4;
Kevin Chync53d9812019-07-30 18:10:30 -0700252
253 // Create a new dialog but do not replace the current one yet.
Kevin Chynf8688a02019-08-27 17:04:05 -0700254 final AuthDialog newDialog = buildDialog(
Kevin Chyn050315f2019-08-08 14:22:54 -0700255 biometricPromptBundle,
256 requireConfirmation,
257 userId,
258 type,
Kevin Chynfc468262019-08-20 17:17:11 -0700259 opPackageName,
260 skipAnimation);
Kevin Chync53d9812019-07-30 18:10:30 -0700261
262 if (newDialog == null) {
263 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800264 return;
265 }
Kevin Chync53d9812019-07-30 18:10:30 -0700266
267 if (DEBUG) {
268 Log.d(TAG, "showDialog, "
269 + " savedState: " + savedState
270 + " mCurrentDialog: " + mCurrentDialog
271 + " newDialog: " + newDialog
272 + " type: " + type);
273 }
274
Kevin Chyn9cf89912019-08-30 13:33:58 -0700275 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700276 // If somehow we're asked to show a dialog, the old one doesn't need to be animated
277 // away. This can happen if the app cancels and re-starts auth during configuration
278 // change. This is ugly because we also have to do things on onConfigurationChanged
279 // here.
280 mCurrentDialog.dismissWithoutCallback(false /* animate */);
281 }
282
283 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
284 mCurrentDialog = newDialog;
Kevin Chyn9cf89912019-08-30 13:33:58 -0700285 mCurrentDialog.show(mWindowManager, savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700286 }
287
Kevin Chyn050315f2019-08-08 14:22:54 -0700288 private void onDialogDismissed(@DismissedReason int reason) {
289 if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700290 if (mCurrentDialog == null) {
291 Log.w(TAG, "Dialog already dismissed");
Kevin Chyn42653e82018-01-19 14:15:46 -0800292 }
293 mReceiver = null;
Kevin Chync53d9812019-07-30 18:10:30 -0700294 mCurrentDialog = null;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800295 }
296
Gus Prevasa7df7b22018-10-30 10:29:34 -0400297 @Override
298 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700299 super.onConfigurationChanged(newConfig);
Kevin Chyne1912712019-01-04 14:22:34 -0800300
301 // Save the state of the current dialog (buttons showing, etc)
Kevin Chyne1912712019-01-04 14:22:34 -0800302 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700303 final Bundle savedState = new Bundle();
Kevin Chyne1912712019-01-04 14:22:34 -0800304 mCurrentDialog.onSaveState(savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700305 mCurrentDialog.dismissWithoutCallback(false /* animate */);
306 mCurrentDialog = null;
Kevin Chyne1912712019-01-04 14:22:34 -0800307
Kevin Chyn27da7182019-09-11 12:17:55 -0700308 // Only show the dialog if necessary. If it was animating out, the dialog is supposed
309 // to send its pending callback immediately.
310 if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
311 != AuthContainerView.STATE_ANIMATING_OUT) {
312 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
313 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400314 }
Kevin Chync53d9812019-07-30 18:10:30 -0700315 }
Kevin Chyne1912712019-01-04 14:22:34 -0800316
Kevin Chynf8688a02019-08-27 17:04:05 -0700317 protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
Kevin Chynfc468262019-08-20 17:17:11 -0700318 int userId, int type, String opPackageName, boolean skipIntro) {
319 if (Settings.Secure.getIntForUser(
320 mContext.getContentResolver(), USE_NEW_DIALOG, userId, 0) != 0) {
321 return new AuthContainerView.Builder(mContext)
322 .setCallback(this)
323 .setBiometricPromptBundle(biometricPromptBundle)
324 .setRequireConfirmation(requireConfirmation)
325 .setUserId(userId)
326 .setOpPackageName(opPackageName)
327 .setSkipIntro(skipIntro)
328 .build(type);
329 } else {
330 return new BiometricDialogView.Builder(mContext)
331 .setCallback(this)
332 .setBiometricPromptBundle(biometricPromptBundle)
333 .setRequireConfirmation(requireConfirmation)
334 .setUserId(userId)
335 .setOpPackageName(opPackageName)
336 .setSkipIntro(skipIntro)
337 .build(type);
338 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400339 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800340}