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