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