1 /*
2  * Copyright (C) 2023 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 
17 package com.android.systemui.biometrics;
18 
19 import static android.app.PendingIntent.FLAG_IMMUTABLE;
20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
21 
22 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
23 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.Notification;
28 import android.app.NotificationChannel;
29 import android.app.NotificationManager;
30 import android.app.PendingIntent;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.hardware.biometrics.BiometricFaceConstants;
35 import android.hardware.biometrics.BiometricSourceType;
36 import android.hardware.biometrics.BiometricStateListener;
37 import android.hardware.face.FaceManager;
38 import android.hardware.fingerprint.FingerprintManager;
39 import android.os.Handler;
40 import android.os.UserHandle;
41 import android.provider.Settings;
42 import android.util.Log;
43 
44 import com.android.keyguard.KeyguardUpdateMonitor;
45 import com.android.keyguard.KeyguardUpdateMonitorCallback;
46 import com.android.systemui.CoreStartable;
47 import com.android.systemui.dagger.SysUISingleton;
48 import com.android.systemui.res.R;
49 import com.android.systemui.statusbar.policy.KeyguardStateController;
50 
51 import java.util.Optional;
52 
53 import javax.inject.Inject;
54 
55 /**
56  * Handles showing system notifications related to biometric unlock.
57  */
58 @SysUISingleton
59 public class BiometricNotificationService implements CoreStartable {
60 
61     private static final String TAG = "BiometricNotificationService";
62     private static final String CHANNEL_ID = "BiometricHiPriNotificationChannel";
63     private static final String CHANNEL_NAME = " Biometric Unlock";
64     private static final int FACE_NOTIFICATION_ID = 1;
65     private static final int FINGERPRINT_NOTIFICATION_ID = 2;
66     private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
67     private static final int REENROLL_REQUIRED = 1;
68     private static final int REENROLL_NOT_REQUIRED = 0;
69 
70     private final Context mContext;
71     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
72     private final KeyguardStateController mKeyguardStateController;
73     private final Handler mHandler;
74     private final NotificationManager mNotificationManager;
75     private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
76     private final FingerprintReEnrollNotification mFingerprintReEnrollNotification;
77     private final FingerprintManager mFingerprintManager;
78     private final FaceManager mFaceManager;
79     private NotificationChannel mNotificationChannel;
80     private boolean mFaceNotificationQueued;
81     private boolean mFingerprintNotificationQueued;
82     private boolean mFingerprintReenrollRequired;
83 
84     private boolean mIsFingerprintReenrollForced;
85 
86     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
87             new KeyguardStateController.Callback() {
88                 private boolean mIsShowing = true;
89                 @Override
90                 public void onKeyguardShowingChanged() {
91                     if (mKeyguardStateController.isShowing()
92                             || mKeyguardStateController.isShowing() == mIsShowing) {
93                         mIsShowing = mKeyguardStateController.isShowing();
94                         return;
95                     }
96                     mIsShowing = mKeyguardStateController.isShowing();
97                     if (isFaceReenrollRequired(mContext) && !mFaceNotificationQueued) {
98                         queueFaceReenrollNotification();
99                     }
100                     if (mFingerprintReenrollRequired && !mFingerprintNotificationQueued) {
101                         mFingerprintReenrollRequired = false;
102                         queueFingerprintReenrollNotification();
103                     }
104                 }
105             };
106 
107     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
108             new KeyguardUpdateMonitorCallback() {
109                 @Override
110                 public void onBiometricError(int msgId, String errString,
111                         BiometricSourceType biometricSourceType) {
112                     if (msgId == BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL
113                             && biometricSourceType == BiometricSourceType.FACE) {
114                         Settings.Secure.putIntForUser(mContext.getContentResolver(),
115                                 Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_REQUIRED,
116                                 UserHandle.USER_CURRENT);
117                     }
118                 }
119 
120                 @Override
121                 public void onBiometricHelp(int msgId, String helpString,
122                         BiometricSourceType biometricSourceType) {
123                     if (biometricSourceType == BiometricSourceType.FINGERPRINT
124                             && mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
125                                     msgId)) {
126                         mFingerprintReenrollRequired = true;
127                         mIsFingerprintReenrollForced =
128                                 mFingerprintReEnrollNotification.isFingerprintReEnrollForced(msgId);
129                     }
130                 }
131             };
132 
133     private final BiometricStateListener mFaceStateListener = new BiometricStateListener() {
134         @Override
135         public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
136             mNotificationManager.cancelAsUser(TAG, FACE_NOTIFICATION_ID, UserHandle.CURRENT);
137         }
138     };
139 
140     private final BiometricStateListener mFingerprintStateListener = new BiometricStateListener() {
141         @Override
142         public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
143             mNotificationManager.cancelAsUser(TAG, FINGERPRINT_NOTIFICATION_ID, UserHandle.CURRENT);
144         }
145     };
146 
147     @Inject
BiometricNotificationService(@onNull Context context, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull KeyguardStateController keyguardStateController, @NonNull Handler handler, @NonNull NotificationManager notificationManager, @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver, @NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification, @Nullable FingerprintManager fingerprintManager, @Nullable FaceManager faceManager)148     public BiometricNotificationService(@NonNull Context context,
149             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
150             @NonNull KeyguardStateController keyguardStateController,
151             @NonNull Handler handler, @NonNull NotificationManager notificationManager,
152             @NonNull BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
153             @NonNull Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification,
154             @Nullable FingerprintManager fingerprintManager,
155             @Nullable FaceManager faceManager) {
156         mContext = context;
157         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
158         mKeyguardStateController = keyguardStateController;
159         mHandler = handler;
160         mNotificationManager = notificationManager;
161         mBroadcastReceiver = biometricNotificationBroadcastReceiver;
162         mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse(
163                 new FingerprintReEnrollNotificationImpl());
164         mFingerprintManager = fingerprintManager;
165         mFaceManager = faceManager;
166     }
167 
168     @Override
start()169     public void start() {
170         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
171         mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
172         mNotificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
173                 NotificationManager.IMPORTANCE_HIGH);
174         final IntentFilter intentFilter = new IntentFilter();
175         intentFilter.addAction(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
176         intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG);
177         mContext.registerReceiver(mBroadcastReceiver, intentFilter,
178                 Context.RECEIVER_EXPORTED_UNAUDITED);
179         if (mFingerprintManager != null) {
180             mFingerprintManager.registerBiometricStateListener(mFingerprintStateListener);
181         }
182         if (mFaceManager != null) {
183             mFaceManager.registerBiometricStateListener(mFaceStateListener);
184         }
185         Settings.Secure.putIntForUser(mContext.getContentResolver(),
186                 Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED,
187                 UserHandle.USER_CURRENT);
188     }
189 
queueFaceReenrollNotification()190     private void queueFaceReenrollNotification() {
191         Log.d(TAG, "Face re-enroll notification queued.");
192         mFaceNotificationQueued = true;
193         final String title = mContext.getString(R.string.face_re_enroll_notification_title);
194         final String content = mContext.getString(
195                 R.string.biometric_re_enroll_notification_content);
196         final String name = mContext.getString(R.string.face_re_enroll_notification_name);
197         mHandler.postDelayed(
198                 () -> showNotification(ACTION_SHOW_FACE_REENROLL_DIALOG, title, content, name,
199                         FACE_NOTIFICATION_ID, false),
200                 SHOW_NOTIFICATION_DELAY_MS);
201     }
202 
queueFingerprintReenrollNotification()203     private void queueFingerprintReenrollNotification() {
204         Log.d(TAG, "Fingerprint re-enroll notification queued.");
205         mFingerprintNotificationQueued = true;
206         final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title);
207         final String content = mContext.getString(
208                 R.string.biometric_re_enroll_notification_content);
209         final String name = mContext.getString(R.string.fingerprint_re_enroll_notification_name);
210         mHandler.postDelayed(
211                 () -> showNotification(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG, title, content,
212                         name, FINGERPRINT_NOTIFICATION_ID, mIsFingerprintReenrollForced),
213                 SHOW_NOTIFICATION_DELAY_MS);
214     }
215 
showNotification(String action, CharSequence title, CharSequence content, CharSequence name, int notificationId, boolean isReenrollForced)216     private void showNotification(String action, CharSequence title, CharSequence content,
217             CharSequence name, int notificationId, boolean isReenrollForced) {
218         if (notificationId == FACE_NOTIFICATION_ID) {
219             mFaceNotificationQueued = false;
220         } else if (notificationId == FINGERPRINT_NOTIFICATION_ID) {
221             mFingerprintNotificationQueued = false;
222         }
223 
224         if (mNotificationManager == null) {
225             Log.e(TAG, "Failed to show notification "
226                     + action + ". Notification manager is null!");
227             return;
228         }
229 
230         final Intent onClickIntent = new Intent(action);
231         onClickIntent.putExtra(BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED,
232                 isReenrollForced);
233 
234         final PendingIntent onClickPendingIntent = PendingIntent.getBroadcastAsUser(mContext,
235                 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT,
236                 UserHandle.CURRENT);
237 
238         final Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
239                 .setCategory(Notification.CATEGORY_SYSTEM)
240                 .setSmallIcon(com.android.internal.R.drawable.ic_lock)
241                 .setContentTitle(title)
242                 .setContentText(content)
243                 .setSubText(name)
244                 .setContentIntent(onClickPendingIntent)
245                 .setAutoCancel(true)
246                 .setLocalOnly(true)
247                 .setOnlyAlertOnce(true)
248                 .setVisibility(Notification.VISIBILITY_SECRET)
249                 .build();
250 
251         mNotificationManager.createNotificationChannel(mNotificationChannel);
252         mNotificationManager.notifyAsUser(TAG, notificationId, notification, UserHandle.CURRENT);
253     }
254 
isFaceReenrollRequired(Context context)255     private boolean isFaceReenrollRequired(Context context) {
256         final int settingValue =
257                 Settings.Secure.getIntForUser(context.getContentResolver(),
258                         Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED,
259                         UserHandle.USER_CURRENT);
260         return settingValue == REENROLL_REQUIRED;
261     }
262 }
263