blob: a8e572216315dadf911513604c4d894bbed0bf09 [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 Chynaae4a152018-01-18 11:48:09 -080032import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080033import android.view.WindowManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080034
Kevin Chyn050315f2019-08-08 14:22:54 -070035import com.android.internal.annotations.VisibleForTesting;
Kevin Chyn42653e82018-01-19 14:15:46 -080036import com.android.internal.os.SomeArgs;
Kevin Chynaae4a152018-01-18 11:48:09 -080037import com.android.systemui.SystemUI;
Kevin Chync53d9812019-07-30 18:10:30 -070038import com.android.systemui.biometrics.ui.BiometricDialogView;
Kevin Chynaae4a152018-01-18 11:48:09 -080039import 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 Chync53d9812019-07-30 18:10:30 -070047public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
48 DialogViewCallback {
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -070049 private static final String TAG = "BiometricDialogImpl";
Kevin Chyn42653e82018-01-19 14:15:46 -080050 private static final boolean DEBUG = true;
51
Kevin Chyn050315f2019-08-08 14:22:54 -070052 private final Injector mInjector;
53
Kevin Chync53d9812019-07-30 18:10:30 -070054 // TODO: These should just be saved from onSaveState
Gus Prevasa7df7b22018-10-30 10:29:34 -040055 private SomeArgs mCurrentDialogArgs;
Kevin Chyn050315f2019-08-08 14:22:54 -070056 @VisibleForTesting
57 BiometricDialog mCurrentDialog;
Kevin Chync53d9812019-07-30 18:10:30 -070058
Kevin Chyn050315f2019-08-08 14:22:54 -070059 private Handler mHandler = new Handler(Looper.getMainLooper());
Kevin Chyn42653e82018-01-19 14:15:46 -080060 private WindowManager mWindowManager;
Kevin Chyn050315f2019-08-08 14:22:54 -070061 @VisibleForTesting
62 IActivityTaskManager mActivityTaskManager;
63 @VisibleForTesting
64 BiometricTaskStackListener mTaskStackListener;
65 @VisibleForTesting
66 IBiometricServiceReceiverInternal mReceiver;
67
68 public class BiometricTaskStackListener extends TaskStackListener {
69 @Override
70 public void onTaskStackChanged() {
71 mHandler.post(mTaskStackChangedRunnable);
72 }
73 }
74
75 private final Runnable mTaskStackChangedRunnable = () -> {
76 if (mCurrentDialog != null) {
77 try {
78 final String clientPackage = mCurrentDialog.getOpPackageName();
79 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
80 final List<ActivityManager.RunningTaskInfo> runningTasks =
81 mActivityTaskManager.getTasks(1);
82 if (!runningTasks.isEmpty()) {
83 final String topPackage = runningTasks.get(0).topActivity.getPackageName();
84 if (!topPackage.contentEquals(clientPackage)) {
85 Log.w(TAG, "Evicting client due to: " + topPackage);
86 mCurrentDialog.dismissWithoutCallback(true /* animate */);
87 mCurrentDialog = null;
88 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
89 mReceiver = null;
90 }
91 }
92 } catch (RemoteException e) {
93 Log.e(TAG, "Remote exception", e);
94 }
95 }
96 };
Kevin Chyn42653e82018-01-19 14:15:46 -080097
Kevin Chync53d9812019-07-30 18:10:30 -070098 @Override
99 public void onTryAgainPressed() {
100 try {
101 mReceiver.onTryAgainPressed();
102 } catch (RemoteException e) {
103 Log.e(TAG, "RemoteException when handling try again", e);
Kevin Chyn5906c172018-07-23 15:43:02 -0700104 }
105 }
106
Kevin Chync53d9812019-07-30 18:10:30 -0700107 @Override
Kevin Chyn050315f2019-08-08 14:22:54 -0700108 public void onDismissed(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700109 switch (reason) {
110 case DialogViewCallback.DISMISSED_USER_CANCELED:
111 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
112 break;
113
114 case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
115 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
116 break;
117
118 case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
119 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
120 break;
121
122 case DialogViewCallback.DISMISSED_AUTHENTICATED:
123 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
Kevin Chync53d9812019-07-30 18:10:30 -0700124 break;
125
126 case DialogViewCallback.DISMISSED_ERROR:
127 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
Kevin Chync53d9812019-07-30 18:10:30 -0700128 break;
Kevin Chyn050315f2019-08-08 14:22:54 -0700129
130 case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
131 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
132 break;
133
Kevin Chync53d9812019-07-30 18:10:30 -0700134 default:
135 Log.e(TAG, "Unhandled reason: " + reason);
136 break;
Kevin Chync94b7db2019-05-15 17:28:16 -0700137 }
Kevin Chync53d9812019-07-30 18:10:30 -0700138 }
139
Kevin Chyn050315f2019-08-08 14:22:54 -0700140 private void sendResultAndCleanUp(@DismissedReason int reason) {
Kevin Chync53d9812019-07-30 18:10:30 -0700141 if (mReceiver == null) {
142 Log.e(TAG, "Receiver is null");
143 return;
144 }
145 try {
Kevin Chyn050315f2019-08-08 14:22:54 -0700146 mReceiver.onDialogDismissed(reason);
Kevin Chync53d9812019-07-30 18:10:30 -0700147 } catch (RemoteException e) {
148 Log.w(TAG, "Remote exception", e);
149 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700150 onDialogDismissed(reason);
151 }
152
153 public static class Injector {
154 IActivityTaskManager getActivityTaskManager() {
155 return ActivityTaskManager.getService();
156 }
157 }
158
159 public BiometricDialogImpl() {
160 this(new Injector());
161 }
162
163 @VisibleForTesting
164 BiometricDialogImpl(Injector injector) {
165 mInjector = injector;
Kevin Chync53d9812019-07-30 18:10:30 -0700166 }
Kevin Chync94b7db2019-05-15 17:28:16 -0700167
Kevin Chynaae4a152018-01-18 11:48:09 -0800168 @Override
169 public void start() {
Kevin Chyne1912712019-01-04 14:22:34 -0800170 final PackageManager pm = mContext.getPackageManager();
171 if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
172 || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
173 || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
Jason Monkd7c98552018-12-04 11:14:50 -0500174 getComponent(CommandQueue.class).addCallback(this);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400175 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Kevin Chyn050315f2019-08-08 14:22:54 -0700176 mActivityTaskManager = mInjector.getActivityTaskManager();
177
178 try {
179 mTaskStackListener = new BiometricTaskStackListener();
180 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
181 } catch (RemoteException e) {
182 Log.w(TAG, "Unable to register task stack listener", e);
183 }
Gus Prevasa7df7b22018-10-30 10:29:34 -0400184 }
185 }
186
Kevin Chynaae4a152018-01-18 11:48:09 -0800187 @Override
Kevin Chyn23289ef2018-11-28 16:32:36 -0800188 public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
Kevin Chyn050315f2019-08-08 14:22:54 -0700189 int type, boolean requireConfirmation, int userId, String opPackageName) {
Kevin Chyn158fefb2019-01-03 18:59:05 -0800190 if (DEBUG) {
191 Log.d(TAG, "showBiometricDialog, type: " + type
192 + ", requireConfirmation: " + requireConfirmation);
193 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800194 SomeArgs args = SomeArgs.obtain();
195 args.arg1 = bundle;
196 args.arg2 = receiver;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700197 args.argi1 = type;
198 args.arg3 = requireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800199 args.argi2 = userId;
Kevin Chyn050315f2019-08-08 14:22:54 -0700200 args.arg4 = opPackageName;
Kevin Chync53d9812019-07-30 18:10:30 -0700201
202 boolean skipAnimation = false;
203 if (mCurrentDialog != null) {
204 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
205 skipAnimation = true;
206 }
Kevin Chyn050315f2019-08-08 14:22:54 -0700207 showDialog(args, skipAnimation, null /* savedState */);
Kevin Chynaae4a152018-01-18 11:48:09 -0800208 }
209
210 @Override
Kevin Chyne674e852019-04-24 12:39:40 -0700211 public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
212 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
213 + " reason: " + failureReason);
214
Kevin Chyne1912712019-01-04 14:22:34 -0800215 if (authenticated) {
Kevin Chync53d9812019-07-30 18:10:30 -0700216 mCurrentDialog.onAuthenticationSucceeded();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700217 } else {
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700218 mCurrentDialog.onAuthenticationFailed(failureReason);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700219 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800220 }
221
Kevin Chync53d9812019-07-30 18:10:30 -0700222 @Override
223 public void onBiometricHelp(String message) {
224 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
225
226 mCurrentDialog.onHelp(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800227 }
228
Kevin Chync53d9812019-07-30 18:10:30 -0700229 @Override
230 public void onBiometricError(String error) {
231 if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
232 mCurrentDialog.onError(error);
Kevin Chyn42653e82018-01-19 14:15:46 -0800233 }
234
Kevin Chync53d9812019-07-30 18:10:30 -0700235 @Override
236 public void hideBiometricDialog() {
237 if (DEBUG) Log.d(TAG, "hideBiometricDialog");
238
Kevin Chyn050315f2019-08-08 14:22:54 -0700239 mCurrentDialog.dismissFromSystemServer();
Kevin Chync53d9812019-07-30 18:10:30 -0700240 }
241
242 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
243 mCurrentDialogArgs = args;
244 final int type = args.argi1;
Kevin Chyn050315f2019-08-08 14:22:54 -0700245 final Bundle biometricPromptBundle = (Bundle) args.arg1;
246 final boolean requireConfirmation = (boolean) args.arg3;
247 final int userId = args.argi2;
248 final String opPackageName = (String) args.arg4;
Kevin Chync53d9812019-07-30 18:10:30 -0700249
250 // Create a new dialog but do not replace the current one yet.
251 final BiometricDialog newDialog = buildDialog(
Kevin Chyn050315f2019-08-08 14:22:54 -0700252 biometricPromptBundle,
253 requireConfirmation,
254 userId,
255 type,
256 opPackageName);
Kevin Chync53d9812019-07-30 18:10:30 -0700257
258 if (newDialog == null) {
259 Log.e(TAG, "Unsupported type: " + type);
Kevin Chyn42653e82018-01-19 14:15:46 -0800260 return;
261 }
Kevin Chync53d9812019-07-30 18:10:30 -0700262
263 if (DEBUG) {
264 Log.d(TAG, "showDialog, "
265 + " savedState: " + savedState
266 + " mCurrentDialog: " + mCurrentDialog
267 + " newDialog: " + newDialog
268 + " type: " + type);
269 }
270
271 if (savedState != null) {
272 // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
273 // even though it may be removed / re-created again
274 newDialog.restoreState(savedState);
275 } else if (mCurrentDialog != null) {
276 // 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;
285 mCurrentDialog.show(mWindowManager, skipAnimation);
286 }
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 Chync53d9812019-07-30 18:10:30 -0700308 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400309 }
Kevin Chync53d9812019-07-30 18:10:30 -0700310 }
Kevin Chyne1912712019-01-04 14:22:34 -0800311
Kevin Chync53d9812019-07-30 18:10:30 -0700312 protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
Kevin Chyn050315f2019-08-08 14:22:54 -0700313 boolean requireConfirmation, int userId, int type, String opPackageName) {
Kevin Chync53d9812019-07-30 18:10:30 -0700314 return new BiometricDialogView.Builder(mContext)
315 .setCallback(this)
316 .setBiometricPromptBundle(biometricPromptBundle)
317 .setRequireConfirmation(requireConfirmation)
318 .setUserId(userId)
Kevin Chyn050315f2019-08-08 14:22:54 -0700319 .setOpPackageName(opPackageName)
Kevin Chync53d9812019-07-30 18:10:30 -0700320 .build(type);
Gus Prevasa7df7b22018-10-30 10:29:34 -0400321 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800322}