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