blob: 6aff3f7010f2e75173334d37ce5809292f5ca689 [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 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;
Kevin Chync53d9812019-07-30 18:10:30 -070039import com.android.systemui.biometrics.ui.BiometricDialogView;
Kevin Chynfc468262019-08-20 17:17:11 -070040import com.android.systemui.biometrics.ui.AuthContainerView;
Kevin Chynaae4a152018-01-18 11:48:09 -080041import com.android.systemui.statusbar.CommandQueue;
42
Kevin Chyn050315f2019-08-08 14:22:54 -070043import java.util.List;
44
Kevin Chyne9275662018-07-23 16:42:06 -070045/**
Kevin Chync53d9812019-07-30 18:10:30 -070046 * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
47 * appropriate biometric UI (e.g. BiometricDialogView).
Kevin Chyne9275662018-07-23 16:42:06 -070048 */
Kevin Chync53d9812019-07-30 18:10:30 -070049public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
50 DialogViewCallback {
Kevin Chynfc468262019-08-20 17:17:11 -070051 private static final String USE_NEW_DIALOG =
52 "com.android.systemui.biometrics.BiometricDialogImpl.USE_NEW_DIALOG";
53
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -070054 private static final String TAG = "BiometricDialogImpl";
Kevin Chyn42653e82018-01-19 14:15:46 -080055 private static final boolean DEBUG = true;
56
Kevin Chyn050315f2019-08-08 14:22:54 -070057 private final Injector mInjector;
58
Kevin Chync53d9812019-07-30 18:10:30 -070059 // TODO: These should just be saved from onSaveState
Gus Prevasa7df7b22018-10-30 10:29:34 -040060 private SomeArgs mCurrentDialogArgs;
Kevin Chyn050315f2019-08-08 14:22:54 -070061 @VisibleForTesting
62 BiometricDialog mCurrentDialog;
Kevin Chync53d9812019-07-30 18:10:30 -070063
Kevin Chyn050315f2019-08-08 14:22:54 -070064 private Handler mHandler = new Handler(Looper.getMainLooper());
Kevin Chyn42653e82018-01-19 14:15:46 -080065 private WindowManager mWindowManager;
Kevin Chyn050315f2019-08-08 14:22:54 -070066 @VisibleForTesting
67 IActivityTaskManager mActivityTaskManager;
68 @VisibleForTesting
69 BiometricTaskStackListener mTaskStackListener;
70 @VisibleForTesting
71 IBiometricServiceReceiverInternal mReceiver;
72
73 public class BiometricTaskStackListener extends TaskStackListener {
74 @Override
75 public void onTaskStackChanged() {
76 mHandler.post(mTaskStackChangedRunnable);
77 }
78 }
79
80 private final Runnable mTaskStackChangedRunnable = () -> {
81 if (mCurrentDialog != null) {
82 try {
83 final String clientPackage = mCurrentDialog.getOpPackageName();
84 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
85 final List<ActivityManager.RunningTaskInfo> runningTasks =
86 mActivityTaskManager.getTasks(1);
87 if (!runningTasks.isEmpty()) {
88 final String topPackage = runningTasks.get(0).topActivity.getPackageName();
89 if (!topPackage.contentEquals(clientPackage)) {
90 Log.w(TAG, "Evicting client due to: " + topPackage);
91 mCurrentDialog.dismissWithoutCallback(true /* animate */);
92 mCurrentDialog = null;
93 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
94 mReceiver = null;
95 }
96 }
97 } catch (RemoteException e) {
98 Log.e(TAG, "Remote exception", e);
99 }
100 }
101 };
Kevin Chyn42653e82018-01-19 14:15:46 -0800102
Kevin Chync53d9812019-07-30 18:10:30 -0700103 @Override
104 public void onTryAgainPressed() {
105 try {
106 mReceiver.onTryAgainPressed();
107 } catch (RemoteException e) {
108 Log.e(TAG, "RemoteException when handling try again", e);
Kevin Chyn5906c172018-07-23 15:43:02 -0700109 }
110 }
111
Kevin Chync53d9812019-07-30 18:10:30 -0700112 @Override
Kevin Chyn050315f2019-08-08 14:22:54 -0700113 public void onDismissed(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700114 switch (reason) {
115 case DialogViewCallback.DISMISSED_USER_CANCELED:
116 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
117 break;
118
119 case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
120 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
121 break;
122
123 case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
124 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
125 break;
126
127 case DialogViewCallback.DISMISSED_AUTHENTICATED:
128 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700129 break;
130
131 case DialogViewCallback.DISMISSED_ERROR:
132 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
Kevin Chync53d9812019-07-30 18:10:30 -0700133 break;
Kevin Chyn050315f2019-08-08 14:22:54 -0700134
135 case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
136 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
137 break;
138
Kevin Chync53d9812019-07-30 18:10:30 -0700139 default:
140 Log.e(TAG, "Unhandled reason: " + reason);
141 break;
Kevin Chync94b7db2019-05-15 17:28:16 -0700142 }
Kevin Chync53d9812019-07-30 18:10:30 -0700143 }
144
Kevin Chyn050315f2019-08-08 14:22:54 -0700145 private void sendResultAndCleanUp(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700146 if (mReceiver == null) {
147 Log.e(TAG, "Receiver is null");
148 return;
149 }
150 try {
Kevin Chyn050315f2019-08-08 14:22:54 -0700151 mReceiver.onDialogDismissed(reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700152 } catch (RemoteException e) {
153 Log.w(TAG, "Remote exception", e);
154 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700155 onDialogDismissed(reason);
156 }
157
158 public static class Injector {
159 IActivityTaskManager getActivityTaskManager() {
160 return ActivityTaskManager.getService();
161 }
162 }
163
164 public BiometricDialogImpl() {
165 this(new Injector());
166 }
167
168 @VisibleForTesting
169 BiometricDialogImpl(Injector injector) {
170 mInjector = injector;
Kevin Chync53d9812019-07-30 18:10:30 -0700171 }
Kevin Chync94b7db2019-05-15 17:28:16 -0700172
Kevin Chynaae4a152018-01-18 11:48:09 -0800173 @Override
174 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800175 final PackageManager pm = mContext.getPackageManager();
176 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
177 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
178 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500179 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400180 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700181 mActivityTaskManager = mInjector.getActivityTaskManager();
182
183 try {
184 mTaskStackListener = new BiometricTaskStackListener();
185 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
186 } catch (RemoteException e) {
187 Log.w(TAG, "Unable to register task stack listener", e);
188 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400189 }
190 }
191
Kevin Chynaae4a152018-01-18 11:48:09 -0800192 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800193 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
Kevin Chyn050315f2019-08-08 14:22:54 -0700194 int type, boolean requireConfirmation, int userId, String opPackageName) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800195 if (DEBUG) {
196 Log.d(TAG, "showBiometricDialog, type: " + type
197 + ", requireConfirmation: " + requireConfirmation);
198 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800199 SomeArgs args = SomeArgs.obtain();
200 args.arg1 = bundle;
201 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700202 args.argi1 = type;
203 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800204 args.argi2 = userId;
Kevin Chyn050315f2019-08-08 14:22:54 -0700205 args.arg4 = opPackageName;
Kevin Chync53d9812019-07-30 18:10:30 -0700206
207 boolean skipAnimation = false;
208 if (mCurrentDialog != null) {
209 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
210 skipAnimation = true;
211 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700212 showDialog(args, skipAnimation, null /* savedState */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800213 }
214
215 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700216 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
217 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
218 + " reason: " + failureReason);
219
Kevin Chyne1912712019-01-04 14:22:34 -0800220 if (authenticated) {
Kevin Chync53d9812019-07-30 18:10:30 -0700221 mCurrentDialog.onAuthenticationSucceeded();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700222 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700223 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700224 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800225 }
226
Kevin Chync53d9812019-07-30 18:10:30 -0700227 @Override
228 public void onBiometricHelp(String message) {
229 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
230
231 mCurrentDialog.onHelp(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800232 }
233
Kevin Chync53d9812019-07-30 18:10:30 -0700234 @Override
235 public void onBiometricError(String error) {
236 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
237 mCurrentDialog.onError(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800238 }
239
Kevin Chync53d9812019-07-30 18:10:30 -0700240 @Override
241 public void hideBiometricDialog() {
242 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
243
Kevin Chyn050315f2019-08-08 14:22:54 -0700244 mCurrentDialog.dismissFromSystemServer();
Kevin Chync53d9812019-07-30 18:10:30 -0700245 }
246
247 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
248 mCurrentDialogArgs = args;
249 final int type = args.argi1;
Kevin Chyn050315f2019-08-08 14:22:54 -0700250 final Bundle biometricPromptBundle = (Bundle) args.arg1;
251 final boolean requireConfirmation = (boolean) args.arg3;
252 final int userId = args.argi2;
253 final String opPackageName = (String) args.arg4;
Kevin Chync53d9812019-07-30 18:10:30 -0700254
255 // Create a new dialog but do not replace the current one yet.
256 final BiometricDialog newDialog = buildDialog(
Kevin Chyn050315f2019-08-08 14:22:54 -0700257 biometricPromptBundle,
258 requireConfirmation,
259 userId,
260 type,
Kevin Chynfc468262019-08-20 17:17:11 -0700261 opPackageName,
262 skipAnimation);
Kevin Chync53d9812019-07-30 18:10:30 -0700263
264 if (newDialog == null) {
265 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800266 return;
267 }
Kevin Chync53d9812019-07-30 18:10:30 -0700268
269 if (DEBUG) {
270 Log.d(TAG, "showDialog, "
271 + " savedState: " + savedState
272 + " mCurrentDialog: " + mCurrentDialog
273 + " newDialog: " + newDialog
274 + " type: " + type);
275 }
276
277 if (savedState != null) {
278 // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
279 // even though it may be removed / re-created again
280 newDialog.restoreState(savedState);
281 } else if (mCurrentDialog != null) {
282 // If somehow we're asked to show a dialog, the old one doesn't need to be animated
283 // away. This can happen if the app cancels and re-starts auth during configuration
284 // change. This is ugly because we also have to do things on onConfigurationChanged
285 // here.
286 mCurrentDialog.dismissWithoutCallback(false /* animate */);
287 }
288
289 mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
290 mCurrentDialog = newDialog;
Kevin Chynfc468262019-08-20 17:17:11 -0700291 mCurrentDialog.show(mWindowManager);
Kevin Chync53d9812019-07-30 18:10:30 -0700292 }
293
Kevin Chyn050315f2019-08-08 14:22:54 -0700294 private void onDialogDismissed(@DismissedReason int reason) {
295 if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700296 if (mCurrentDialog == null) {
297 Log.w(TAG, "Dialog already dismissed");
Kevin Chyn42653e82018-01-19 14:15:46 -0800298 }
299 mReceiver = null;
Kevin Chync53d9812019-07-30 18:10:30 -0700300 mCurrentDialog = null;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800301 }
302
Gus Prevasa7df7b22018-10-30 10:29:34 -0400303 @Override
304 protected void onConfigurationChanged(Configuration newConfig) {
Kevin Chyn02129b12018-11-01 16:47:12 -0700305 super.onConfigurationChanged(newConfig);
Kevin Chyne1912712019-01-04 14:22:34 -0800306
307 // Save the state of the current dialog (buttons showing, etc)
Kevin Chyne1912712019-01-04 14:22:34 -0800308 if (mCurrentDialog != null) {
Kevin Chync53d9812019-07-30 18:10:30 -0700309 final Bundle savedState = new Bundle();
Kevin Chyne1912712019-01-04 14:22:34 -0800310 mCurrentDialog.onSaveState(savedState);
Kevin Chync53d9812019-07-30 18:10:30 -0700311 mCurrentDialog.dismissWithoutCallback(false /* animate */);
312 mCurrentDialog = null;
Kevin Chyne1912712019-01-04 14:22:34 -0800313
Kevin Chync53d9812019-07-30 18:10:30 -0700314 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400315 }
Kevin Chync53d9812019-07-30 18:10:30 -0700316 }
Kevin Chyne1912712019-01-04 14:22:34 -0800317
Kevin Chynfc468262019-08-20 17:17:11 -0700318 protected BiometricDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
319 int userId, int type, String opPackageName, boolean skipIntro) {
320 if (Settings.Secure.getIntForUser(
321 mContext.getContentResolver(), USE_NEW_DIALOG, userId, 0) != 0) {
322 return new AuthContainerView.Builder(mContext)
323 .setCallback(this)
324 .setBiometricPromptBundle(biometricPromptBundle)
325 .setRequireConfirmation(requireConfirmation)
326 .setUserId(userId)
327 .setOpPackageName(opPackageName)
328 .setSkipIntro(skipIntro)
329 .build(type);
330 } else {
331 return new BiometricDialogView.Builder(mContext)
332 .setCallback(this)
333 .setBiometricPromptBundle(biometricPromptBundle)
334 .setRequireConfirmation(requireConfirmation)
335 .setUserId(userId)
336 .setOpPackageName(opPackageName)
337 .setSkipIntro(skipIntro)
338 .build(type);
339 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400340 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800341}