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