1 /* 2 * Copyright (C) 2020 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.settings.biometrics; 18 19 import android.app.Activity; 20 import android.app.PendingIntent; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentSender; 25 import android.hardware.biometrics.SensorProperties; 26 import android.hardware.face.FaceManager; 27 import android.hardware.face.FaceSensorPropertiesInternal; 28 import android.os.storage.StorageManager; 29 import android.util.Log; 30 import android.view.Surface; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.fragment.app.FragmentActivity; 35 36 import com.android.internal.widget.LockPatternUtils; 37 import com.android.internal.widget.VerifyCredentialResponse; 38 import com.android.settings.SetupWizardUtils; 39 import com.android.settings.biometrics.face.FaceEnrollIntroduction; 40 import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor; 41 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction; 42 import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction; 43 import com.android.settings.password.ChooseLockGeneric; 44 import com.android.settings.password.ChooseLockSettingsHelper; 45 import com.android.settings.password.SetupChooseLockGeneric; 46 47 import com.google.android.setupcompat.util.WizardManagerHelper; 48 49 /** 50 * Common biometric utilities. 51 */ 52 public class BiometricUtils { 53 private static final String TAG = "BiometricUtils"; 54 /** 55 * Given the result from confirming or choosing a credential, request Gatekeeper to generate 56 * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. 57 * 58 * @param context Caller's context 59 * @param result The onActivityResult intent from ChooseLock* or ConfirmLock* 60 * @param userId User ID that the credential/biometric operation applies to 61 * @param challenge Unique biometric challenge from FingerprintManager/FaceManager 62 * @return 63 */ requestGatekeeperHat(@onNull Context context, @NonNull Intent result, int userId, long challenge)64 public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result, 65 int userId, long challenge) { 66 if (!containsGatekeeperPasswordHandle(result)) { 67 throw new IllegalStateException("Gatekeeper Password is missing!!"); 68 } 69 final long gatekeeperPasswordHandle = result.getLongExtra( 70 ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); 71 return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge); 72 } 73 requestGatekeeperHat(@onNull Context context, long gkPwHandle, int userId, long challenge)74 public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId, 75 long challenge) { 76 final LockPatternUtils utils = new LockPatternUtils(context); 77 final VerifyCredentialResponse response = utils.verifyGatekeeperPasswordHandle(gkPwHandle, 78 challenge, userId); 79 if (!response.isMatched()) { 80 throw new IllegalStateException("Unable to request Gatekeeper HAT"); 81 } 82 return response.getGatekeeperHAT(); 83 } 84 containsGatekeeperPasswordHandle(@ullable Intent data)85 public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { 86 return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); 87 } 88 getGatekeeperPasswordHandle(@onNull Intent data)89 public static long getGatekeeperPasswordHandle(@NonNull Intent data) { 90 return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); 91 } 92 93 /** 94 * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the 95 * gatekeeper password associated with a previous 96 * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} 97 * 98 * @param context Caller's context 99 * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* 100 */ removeGatekeeperPasswordHandle(@onNull Context context, @Nullable Intent data)101 public static void removeGatekeeperPasswordHandle(@NonNull Context context, 102 @Nullable Intent data) { 103 if (data == null) { 104 return; 105 } 106 if (!containsGatekeeperPasswordHandle(data)) { 107 return; 108 } 109 removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data)); 110 } 111 removeGatekeeperPasswordHandle(@onNull Context context, long handle)112 public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) { 113 final LockPatternUtils utils = new LockPatternUtils(context); 114 utils.removeGatekeeperPasswordHandle(handle); 115 Log.d(TAG, "Removed handle"); 116 } 117 118 /** 119 * @param context caller's context 120 * @param activityIntent The intent that started the caller's activity 121 * @return Intent for starting ChooseLock* 122 */ getChooseLockIntent(@onNull Context context, @NonNull Intent activityIntent)123 public static Intent getChooseLockIntent(@NonNull Context context, 124 @NonNull Intent activityIntent) { 125 if (WizardManagerHelper.isAnySetupWizard(activityIntent)) { 126 // Default to PIN lock in setup wizard 127 Intent intent = new Intent(context, SetupChooseLockGeneric.class); 128 if (StorageManager.isFileEncryptedNativeOrEmulated()) { 129 intent.putExtra( 130 LockPatternUtils.PASSWORD_TYPE_KEY, 131 DevicePolicyManager.PASSWORD_QUALITY_NUMERIC); 132 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment 133 .EXTRA_SHOW_OPTIONS_BUTTON, true); 134 } 135 WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); 136 return intent; 137 } else { 138 return new Intent(context, ChooseLockGeneric.class); 139 } 140 } 141 142 /** 143 * @param context caller's context 144 * @param activityIntent The intent that started the caller's activity 145 * @return Intent for starting FingerprintEnrollFindSensor 146 */ getFingerprintFindSensorIntent(@onNull Context context, @NonNull Intent activityIntent)147 public static Intent getFingerprintFindSensorIntent(@NonNull Context context, 148 @NonNull Intent activityIntent) { 149 Intent intent = new Intent(context, FingerprintEnrollFindSensor.class); 150 SetupWizardUtils.copySetupExtras(activityIntent, intent); 151 return intent; 152 } 153 154 /** 155 * @param context caller's context 156 * @param activityIntent The intent that started the caller's activity 157 * @return Intent for starting FingerprintEnrollIntroduction 158 */ getFingerprintIntroIntent(@onNull Context context, @NonNull Intent activityIntent)159 public static Intent getFingerprintIntroIntent(@NonNull Context context, 160 @NonNull Intent activityIntent) { 161 if (WizardManagerHelper.isAnySetupWizard(activityIntent)) { 162 Intent intent = new Intent(context, SetupFingerprintEnrollIntroduction.class); 163 WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); 164 return intent; 165 } else { 166 return new Intent(context, FingerprintEnrollIntroduction.class); 167 } 168 } 169 170 /** 171 * @param context caller's context 172 * @param activityIntent The intent that started the caller's activity 173 * @return Intent for starting FaceEnrollIntroduction 174 */ getFaceIntroIntent(@onNull Context context, @NonNull Intent activityIntent)175 public static Intent getFaceIntroIntent(@NonNull Context context, 176 @NonNull Intent activityIntent) { 177 final Intent intent = new Intent(context, FaceEnrollIntroduction.class); 178 WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); 179 return intent; 180 } 181 182 /** 183 * Start an activity that prompts the user to hand the device to their parent or guardian. 184 * @param context caller's context 185 * @param activityIntent The intent that started the caller's activity 186 * @return Intent for starting BiometricHandoffActivity 187 */ getHandoffToParentIntent(@onNull Context context, @NonNull Intent activityIntent)188 public static Intent getHandoffToParentIntent(@NonNull Context context, 189 @NonNull Intent activityIntent) { 190 final Intent intent = new Intent(context, BiometricHandoffActivity.class); 191 WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); 192 return intent; 193 } 194 195 /** 196 * @param activity Reference to the calling activity, used to startActivity 197 * @param intent Intent pointing to the enrollment activity 198 * @param requestCode If non-zero, will invoke startActivityForResult instead of startActivity 199 * @param hardwareAuthToken HardwareAuthToken from Gatekeeper 200 * @param userId User to request enrollment for 201 */ launchEnrollForResult(@onNull FragmentActivity activity, @NonNull Intent intent, int requestCode, @Nullable byte[] hardwareAuthToken, @Nullable Long gkPwHandle, int userId)202 public static void launchEnrollForResult(@NonNull FragmentActivity activity, 203 @NonNull Intent intent, int requestCode, 204 @Nullable byte[] hardwareAuthToken, @Nullable Long gkPwHandle, int userId) { 205 if (hardwareAuthToken != null) { 206 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 207 hardwareAuthToken); 208 } 209 if (gkPwHandle != null) { 210 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, (long) gkPwHandle); 211 } 212 213 if (activity instanceof BiometricEnrollActivity.InternalActivity) { 214 intent.putExtra(Intent.EXTRA_USER_ID, userId); 215 } 216 217 if (requestCode != 0) { 218 activity.startActivityForResult(intent, requestCode); 219 } else { 220 activity.startActivity(intent); 221 activity.finish(); 222 } 223 } 224 225 /** 226 * @param activity Activity that we want to check 227 * @return True if the activity is going through a multi-biometric enrollment flow. 228 */ isMultiBiometricEnrollmentFlow(@onNull Activity activity)229 public static boolean isMultiBiometricEnrollmentFlow(@NonNull Activity activity) { 230 return activity.getIntent().hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE); 231 } 232 copyMultiBiometricExtras(@onNull Intent fromIntent, @NonNull Intent toIntent)233 public static void copyMultiBiometricExtras(@NonNull Intent fromIntent, 234 @NonNull Intent toIntent) { 235 final PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra( 236 MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, null); 237 if (pendingIntent != null) { 238 toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, pendingIntent); 239 } 240 } 241 242 /** 243 * If the current biometric enrollment (e.g. face) should be followed by another one (e.g. 244 * fingerprint) (see {@link #isMultiBiometricEnrollmentFlow(Activity)}), retrieves the 245 * PendingIntent pointing to the next enrollment and starts it. The caller will receive the 246 * result in onActivityResult. 247 * @return true if the next enrollment was started 248 */ tryStartingNextBiometricEnroll(@onNull Activity activity, int requestCode, String debugReason)249 public static boolean tryStartingNextBiometricEnroll(@NonNull Activity activity, 250 int requestCode, String debugReason) { 251 252 Log.d(TAG, "tryStartingNextBiometricEnroll, debugReason: " + debugReason); 253 final PendingIntent pendingIntent = (PendingIntent) activity.getIntent() 254 .getExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE); 255 if (pendingIntent != null) { 256 try { 257 Log.d(TAG, "Starting pendingIntent: " + pendingIntent); 258 IntentSender intentSender = pendingIntent.getIntentSender(); 259 activity.startIntentSenderForResult(intentSender, requestCode, 260 null /* fillInIntent */, 0 /* flagMask */, 0 /* flagValues */, 261 0 /* extraFlags */); 262 return true; 263 } catch (IntentSender.SendIntentException e) { 264 Log.e(TAG, "Pending intent canceled: " + e); 265 } 266 } 267 return false; 268 } 269 270 /** 271 * Returns {@code true} if the screen is going into a landscape mode and the angle is equal to 272 * 270. 273 * @param context Context that we use to get the display this context is associated with 274 * @return True if the angle of the rotation is equal to 270. 275 */ isReverseLandscape(@onNull Context context)276 public static boolean isReverseLandscape(@NonNull Context context) { 277 return context.getDisplay().getRotation() == Surface.ROTATION_270; 278 } 279 280 /** 281 * @param faceManager 282 * @return True if at least one sensor is set as a convenience. 283 */ isConvenience(@onNull FaceManager faceManager)284 public static boolean isConvenience(@NonNull FaceManager faceManager) { 285 for (FaceSensorPropertiesInternal props : faceManager.getSensorPropertiesInternal()) { 286 if (props.sensorStrength == SensorProperties.STRENGTH_CONVENIENCE) { 287 return true; 288 } 289 } 290 return false; 291 } 292 293 /** 294 * Returns {@code true} if the screen is going into a landscape mode and the angle is equal to 295 * 90. 296 * @param context Context that we use to get the display this context is associated with 297 * @return True if the angle of the rotation is equal to 90. 298 */ isLandscape(@onNull Context context)299 public static boolean isLandscape(@NonNull Context context) { 300 return context.getDisplay().getRotation() == Surface.ROTATION_90; 301 } 302 } 303