Loading src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +33 −3 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; Loading Loading @@ -58,6 +59,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String KEY_SCROLLED_TO_BOTTOM = "scrolled"; private GatekeeperPasswordProvider mGatekeeperPasswordProvider; private UserManager mUserManager; private boolean mHasPassword; private boolean mBiometricUnlockDisabledByAdmin; Loading Loading @@ -178,7 +180,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mErrorText = getErrorTextView(); mUserManager = UserManager.get(this); mUserManager = getUserManager(); updatePasswordQuality(); if (!mConfirmingCredentials) { Loading Loading @@ -253,8 +255,28 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase return super.shouldFinishWhenBackgrounded() && !mConfirmingCredentials && !mNextClicked; } @VisibleForTesting @NonNull protected GatekeeperPasswordProvider getGatekeeperPasswordProvider() { if (mGatekeeperPasswordProvider == null) { mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(getLockPatternUtils()); } return mGatekeeperPasswordProvider; } @VisibleForTesting protected UserManager getUserManager() { return UserManager.get(this); } @VisibleForTesting @NonNull protected LockPatternUtils getLockPatternUtils() { return new LockPatternUtils(this); } private void updatePasswordQuality() { final int passwordQuality = new LockPatternUtils(this) final int passwordQuality = getLockPatternUtils() .getActivePasswordQuality(mUserManager.getCredentialOwnerProfile(mUserId)); mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } Loading Loading @@ -301,6 +323,14 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); } /** * Returns the intent extra data for setResult(), null means nothing need to been sent back */ @Nullable protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { return activityResultIntent; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, Loading @@ -310,7 +340,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase && BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this); if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { if (isResultFinished(resultCode)) { handleBiometricResultSkipOrFinished(resultCode, data); handleBiometricResultSkipOrFinished(resultCode, getSetResultIntentExtra(data)); } else if (isResultSkipped(resultCode)) { if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) { Loading src/com/android/settings/biometrics/BiometricUtils.java +22 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,8 @@ public class BiometricUtils { }; /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. * * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * Loading @@ -83,6 +85,7 @@ public class BiometricUtils { * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match * @throws IllegalStateException if Gatekeeper Password is missing */ @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result, int userId, long challenge) { if (!containsGatekeeperPasswordHandle(result)) { Loading @@ -93,6 +96,10 @@ public class BiometricUtils { return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId, long challenge) { final LockPatternUtils utils = new LockPatternUtils(context); Loading @@ -104,15 +111,25 @@ public class BiometricUtils { return response.getGatekeeperHAT(); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static long getGatekeeperPasswordHandle(@NonNull Intent data) { return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. * * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} Loading @@ -120,6 +137,7 @@ public class BiometricUtils { * @param context Caller's context * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* */ @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, @Nullable Intent data) { if (data == null) { Loading @@ -131,6 +149,10 @@ public class BiometricUtils { removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data)); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) { final LockPatternUtils utils = new LockPatternUtils(context); utils.removeGatekeeperPasswordHandle(handle); Loading src/com/android/settings/biometrics/GatekeeperPasswordProvider.java 0 → 100644 +128 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.biometrics; import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.password.ChooseLockSettingsHelper; /** * Gatekeeper hat related methods */ public class GatekeeperPasswordProvider { private static final String TAG = "GatekeeperPasswordProvider"; private final LockPatternUtils mLockPatternUtils; public GatekeeperPasswordProvider(LockPatternUtils lockPatternUtils) { mLockPatternUtils = lockPatternUtils; } /** * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * * @param result The onActivityResult intent from ChooseLock* or ConfirmLock* * @param challenge Unique biometric challenge from FingerprintManager/FaceManager * @param userId User ID that the credential/biometric operation applies to * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match * @throws IllegalStateException if Gatekeeper Password is missing */ public byte[] requestGatekeeperHat(@NonNull Intent result, long challenge, int userId) { if (!containsGatekeeperPasswordHandle(result)) { throw new IllegalStateException("Gatekeeper Password is missing!!"); } final long gatekeeperPasswordHandle = result.getLongExtra( ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); return requestGatekeeperHat(gatekeeperPasswordHandle, challenge, userId); } /** * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * * @param gkPwHandle The Gatekeeper password handle from ChooseLock* or ConfirmLock* * @param challenge Unique biometric challenge from FingerprintManager/FaceManager * @param userId User ID that the credential/biometric operation applies to * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match */ public byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId) { final VerifyCredentialResponse response = mLockPatternUtils.verifyGatekeeperPasswordHandle( gkPwHandle, challenge, userId); if (!response.isMatched()) { throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT"); } return response.getGatekeeperHAT(); } /** * Intent data contains gatekeeper password handle or not */ public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } /** * Returns the gatekeeper password handle from intent */ public static long getGatekeeperPasswordHandle(@NonNull Intent data) { return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); } /** * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} * * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* * @param alsoRemoveItFromIntent set it to true if gkPwHandle needs to be removed from intent */ public void removeGatekeeperPasswordHandle(@Nullable Intent data, boolean alsoRemoveItFromIntent) { if (data == null) { return; } if (!containsGatekeeperPasswordHandle(data)) { return; } removeGatekeeperPasswordHandle(getGatekeeperPasswordHandle(data)); if (alsoRemoveItFromIntent) { data.removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } } /** * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} * * @param handle The Gatekeeper password handle from ChooseLock* or ConfirmLock* */ public void removeGatekeeperPasswordHandle(long handle) { mLockPatternUtils.removeGatekeeperPasswordHandle(handle); Log.d(TAG, "Removed handle"); } } src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +43 −8 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.HelpUtils; Loading @@ -68,7 +69,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onCreate(Bundle savedInstanceState) { mFingerprintManager = Utils.getFingerprintManagerOrNull(this); mFingerprintManager = getFingerprintManager(); if (mFingerprintManager == null) { Log.e(TAG, "Null FingerprintManager"); finish(); Loading Loading @@ -127,11 +128,50 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { final ScrollView scrollView = findViewById(R.id.sud_scroll_view); scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); final Intent intent = getIntent(); if (mFromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); getNextButton().setEnabled(false); getChallenge(((sensorId, userId, challenge) -> { if (isFinishing()) { // Do nothing if activity is finishing Log.w(TAG, "activity finished before challenge callback launched."); return; } mSensorId = sensorId; mChallenge = challenge; final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider(); mToken = provider.requestGatekeeperHat(intent, challenge, mUserId); provider.removeGatekeeperPasswordHandle(intent, true); getNextButton().setEnabled(true); })); } } @VisibleForTesting @Nullable protected FingerprintManager getFingerprintManager() { return Utils.getFingerprintManagerOrNull(this); } /** * Returns the intent extra data for setResult(), null means nothing need to been sent back */ @Nullable @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { Intent intent = super.getSetResultIntentExtra(activityResultIntent); if (mFromSettingsSummary && mToken != null && mChallenge != -1L) { if (intent == null) { intent = new Intent(); } intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); } return intent; } @Override Loading Loading @@ -295,11 +335,6 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void getChallenge(GenerateChallengeCallback callback) { mFingerprintManager = Utils.getFingerprintManagerOrNull(this); if (mFingerprintManager == null) { callback.onChallengeGenerated(0, 0, 0L); return; } mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); } Loading src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +61 −22 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; import android.app.Activity; import android.app.Dialog; Loading Loading @@ -60,11 +61,13 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; Loading Loading @@ -410,7 +413,7 @@ public class FingerprintSettings extends SubSettings { launchChooseOrConfirmLock(); } else if (!mHasFirstEnrolled) { mIsEnrolling = true; addFirstFingerprint(); addFirstFingerprint(null); } } updateFooterColumns(activity); Loading Loading @@ -776,7 +779,7 @@ public class FingerprintSettings extends SubSettings { if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { mLaunchedConfirm = false; if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (!mHasFirstEnrolled && !mIsEnrolling) { final Activity activity = getActivity(); if (activity != null) { Loading @@ -784,21 +787,34 @@ public class FingerprintSettings extends SubSettings { activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); } } // To have smoother animation, change flow to let next visible activity // to generateChallenge, then pass it back through activity result. // Token and challenge will be updated later through the activity result // of AUTO_ADD_FIRST_FINGERPRINT_REQUEST. mIsEnrolling = true; addFirstFingerprint( BiometricUtils.getGatekeeperPasswordHandle(data)); } else { mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { mToken = BiometricUtils.requestGatekeeperHat(getActivity(), data, mUserId, challenge); final Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { // Stop everything Log.w(TAG, "activity detach or finishing"); return; } final GatekeeperPasswordProvider provider = new GatekeeperPasswordProvider( new LockPatternUtils(activity)); mToken = provider.requestGatekeeperHat(data, challenge, mUserId); mChallenge = challenge; BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), data); provider.removeGatekeeperPasswordHandle(data, false); updateAddPreference(); if (!mHasFirstEnrolled && !mIsEnrolling) { mIsEnrolling = true; addFirstFingerprint(); } }); } } else { Log.d(TAG, "Data null or GK PW missing"); finish(); Loading @@ -815,12 +831,29 @@ public class FingerprintSettings extends SubSettings { activity.finish(); } } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { mIsEnrolling = false; mHasFirstEnrolled = true; if (resultCode != RESULT_FINISHED) { Log.d(TAG, "Add first fingerprint fail, result:" + resultCode); if (resultCode != RESULT_FINISHED || data == null) { Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode); finish(); return; } mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); if (mToken == null) { Log.w(TAG, "Add first fingerprint, null token"); finish(); return; } mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L); if (mChallenge == -1L) { Log.w(TAG, "Add first fingerprint, invalid challenge"); finish(); return; } mIsEnrolling = false; mHasFirstEnrolled = true; updateAddPreference(); } } Loading Loading @@ -892,7 +925,7 @@ public class FingerprintSettings extends SubSettings { } } private void addFirstFingerprint() { private void addFirstFingerprint(@Nullable Long gkPwHandle) { Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, FeatureFlagUtils.isEnabled(getActivity(), Loading @@ -906,7 +939,13 @@ public class FingerprintSettings extends SubSettings { SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); if (gkPwHandle != null) { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle.longValue()); } else { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge); } startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); } Loading Loading
src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +33 −3 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; Loading Loading @@ -58,6 +59,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String KEY_SCROLLED_TO_BOTTOM = "scrolled"; private GatekeeperPasswordProvider mGatekeeperPasswordProvider; private UserManager mUserManager; private boolean mHasPassword; private boolean mBiometricUnlockDisabledByAdmin; Loading Loading @@ -178,7 +180,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mErrorText = getErrorTextView(); mUserManager = UserManager.get(this); mUserManager = getUserManager(); updatePasswordQuality(); if (!mConfirmingCredentials) { Loading Loading @@ -253,8 +255,28 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase return super.shouldFinishWhenBackgrounded() && !mConfirmingCredentials && !mNextClicked; } @VisibleForTesting @NonNull protected GatekeeperPasswordProvider getGatekeeperPasswordProvider() { if (mGatekeeperPasswordProvider == null) { mGatekeeperPasswordProvider = new GatekeeperPasswordProvider(getLockPatternUtils()); } return mGatekeeperPasswordProvider; } @VisibleForTesting protected UserManager getUserManager() { return UserManager.get(this); } @VisibleForTesting @NonNull protected LockPatternUtils getLockPatternUtils() { return new LockPatternUtils(this); } private void updatePasswordQuality() { final int passwordQuality = new LockPatternUtils(this) final int passwordQuality = getLockPatternUtils() .getActivePasswordQuality(mUserManager.getCredentialOwnerProfile(mUserId)); mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } Loading Loading @@ -301,6 +323,14 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST); } /** * Returns the intent extra data for setResult(), null means nothing need to been sent back */ @Nullable protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { return activityResultIntent; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, Loading @@ -310,7 +340,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase && BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this); if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { if (isResultFinished(resultCode)) { handleBiometricResultSkipOrFinished(resultCode, data); handleBiometricResultSkipOrFinished(resultCode, getSetResultIntentExtra(data)); } else if (isResultSkipped(resultCode)) { if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) { Loading
src/com/android/settings/biometrics/BiometricUtils.java +22 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,8 @@ public class BiometricUtils { }; /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. * * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * Loading @@ -83,6 +85,7 @@ public class BiometricUtils { * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match * @throws IllegalStateException if Gatekeeper Password is missing */ @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result, int userId, long challenge) { if (!containsGatekeeperPasswordHandle(result)) { Loading @@ -93,6 +96,10 @@ public class BiometricUtils { return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId, long challenge) { final LockPatternUtils utils = new LockPatternUtils(context); Loading @@ -104,15 +111,25 @@ public class BiometricUtils { return response.getGatekeeperHAT(); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static long getGatekeeperPasswordHandle(@NonNull Intent data) { return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. * * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} Loading @@ -120,6 +137,7 @@ public class BiometricUtils { * @param context Caller's context * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* */ @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, @Nullable Intent data) { if (data == null) { Loading @@ -131,6 +149,10 @@ public class BiometricUtils { removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data)); } /** * @deprecated Use {@link com.android.settings.biometrics.GatekeeperPasswordProvider} instead. */ @Deprecated public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) { final LockPatternUtils utils = new LockPatternUtils(context); utils.removeGatekeeperPasswordHandle(handle); Loading
src/com/android/settings/biometrics/GatekeeperPasswordProvider.java 0 → 100644 +128 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.biometrics; import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.password.ChooseLockSettingsHelper; /** * Gatekeeper hat related methods */ public class GatekeeperPasswordProvider { private static final String TAG = "GatekeeperPasswordProvider"; private final LockPatternUtils mLockPatternUtils; public GatekeeperPasswordProvider(LockPatternUtils lockPatternUtils) { mLockPatternUtils = lockPatternUtils; } /** * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * * @param result The onActivityResult intent from ChooseLock* or ConfirmLock* * @param challenge Unique biometric challenge from FingerprintManager/FaceManager * @param userId User ID that the credential/biometric operation applies to * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match * @throws IllegalStateException if Gatekeeper Password is missing */ public byte[] requestGatekeeperHat(@NonNull Intent result, long challenge, int userId) { if (!containsGatekeeperPasswordHandle(result)) { throw new IllegalStateException("Gatekeeper Password is missing!!"); } final long gatekeeperPasswordHandle = result.getLongExtra( ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); return requestGatekeeperHat(gatekeeperPasswordHandle, challenge, userId); } /** * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. * * @param gkPwHandle The Gatekeeper password handle from ChooseLock* or ConfirmLock* * @param challenge Unique biometric challenge from FingerprintManager/FaceManager * @param userId User ID that the credential/biometric operation applies to * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match */ public byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId) { final VerifyCredentialResponse response = mLockPatternUtils.verifyGatekeeperPasswordHandle( gkPwHandle, challenge, userId); if (!response.isMatched()) { throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT"); } return response.getGatekeeperHAT(); } /** * Intent data contains gatekeeper password handle or not */ public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) { return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } /** * Returns the gatekeeper password handle from intent */ public static long getGatekeeperPasswordHandle(@NonNull Intent data) { return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L); } /** * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} * * @param data The onActivityResult intent from ChooseLock* or ConfirmLock* * @param alsoRemoveItFromIntent set it to true if gkPwHandle needs to be removed from intent */ public void removeGatekeeperPasswordHandle(@Nullable Intent data, boolean alsoRemoveItFromIntent) { if (data == null) { return; } if (!containsGatekeeperPasswordHandle(data)) { return; } removeGatekeeperPasswordHandle(getGatekeeperPasswordHandle(data)); if (alsoRemoveItFromIntent) { data.removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); } } /** * Requests {@link com.android.server.locksettings.LockSettingsService} to remove the * gatekeeper password associated with a previous * {@link ChooseLockSettingsHelper.Builder#setRequestGatekeeperPasswordHandle(boolean)} * * @param handle The Gatekeeper password handle from ChooseLock* or ConfirmLock* */ public void removeGatekeeperPasswordHandle(long handle) { mLockPatternUtils.removeGatekeeperPasswordHandle(handle); Log.d(TAG, "Removed handle"); } }
src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +43 −8 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.HelpUtils; Loading @@ -68,7 +69,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onCreate(Bundle savedInstanceState) { mFingerprintManager = Utils.getFingerprintManagerOrNull(this); mFingerprintManager = getFingerprintManager(); if (mFingerprintManager == null) { Log.e(TAG, "Null FingerprintManager"); finish(); Loading Loading @@ -127,11 +128,50 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { final ScrollView scrollView = findViewById(R.id.sud_scroll_view); scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); final Intent intent = getIntent(); if (mFromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); getNextButton().setEnabled(false); getChallenge(((sensorId, userId, challenge) -> { if (isFinishing()) { // Do nothing if activity is finishing Log.w(TAG, "activity finished before challenge callback launched."); return; } mSensorId = sensorId; mChallenge = challenge; final GatekeeperPasswordProvider provider = getGatekeeperPasswordProvider(); mToken = provider.requestGatekeeperHat(intent, challenge, mUserId); provider.removeGatekeeperPasswordHandle(intent, true); getNextButton().setEnabled(true); })); } } @VisibleForTesting @Nullable protected FingerprintManager getFingerprintManager() { return Utils.getFingerprintManagerOrNull(this); } /** * Returns the intent extra data for setResult(), null means nothing need to been sent back */ @Nullable @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); protected Intent getSetResultIntentExtra(@Nullable Intent activityResultIntent) { Intent intent = super.getSetResultIntentExtra(activityResultIntent); if (mFromSettingsSummary && mToken != null && mChallenge != -1L) { if (intent == null) { intent = new Intent(); } intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); } return intent; } @Override Loading Loading @@ -295,11 +335,6 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void getChallenge(GenerateChallengeCallback callback) { mFingerprintManager = Utils.getFingerprintManagerOrNull(this); if (mFingerprintManager == null) { callback.onChallengeGenerated(0, 0, 0L); return; } mFingerprintManager.generateChallenge(mUserId, callback::onChallengeGenerated); } Loading
src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +61 −22 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE; import android.app.Activity; import android.app.Dialog; Loading Loading @@ -60,11 +61,13 @@ import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.GatekeeperPasswordProvider; import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; Loading Loading @@ -410,7 +413,7 @@ public class FingerprintSettings extends SubSettings { launchChooseOrConfirmLock(); } else if (!mHasFirstEnrolled) { mIsEnrolling = true; addFirstFingerprint(); addFirstFingerprint(null); } } updateFooterColumns(activity); Loading Loading @@ -776,7 +779,7 @@ public class FingerprintSettings extends SubSettings { if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) { mLaunchedConfirm = false; if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) { if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (BiometricUtils.containsGatekeeperPasswordHandle(data)) { if (!mHasFirstEnrolled && !mIsEnrolling) { final Activity activity = getActivity(); if (activity != null) { Loading @@ -784,21 +787,34 @@ public class FingerprintSettings extends SubSettings { activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); } } // To have smoother animation, change flow to let next visible activity // to generateChallenge, then pass it back through activity result. // Token and challenge will be updated later through the activity result // of AUTO_ADD_FIRST_FINGERPRINT_REQUEST. mIsEnrolling = true; addFirstFingerprint( BiometricUtils.getGatekeeperPasswordHandle(data)); } else { mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { mToken = BiometricUtils.requestGatekeeperHat(getActivity(), data, mUserId, challenge); final Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { // Stop everything Log.w(TAG, "activity detach or finishing"); return; } final GatekeeperPasswordProvider provider = new GatekeeperPasswordProvider( new LockPatternUtils(activity)); mToken = provider.requestGatekeeperHat(data, challenge, mUserId); mChallenge = challenge; BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), data); provider.removeGatekeeperPasswordHandle(data, false); updateAddPreference(); if (!mHasFirstEnrolled && !mIsEnrolling) { mIsEnrolling = true; addFirstFingerprint(); } }); } } else { Log.d(TAG, "Data null or GK PW missing"); finish(); Loading @@ -815,12 +831,29 @@ public class FingerprintSettings extends SubSettings { activity.finish(); } } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { mIsEnrolling = false; mHasFirstEnrolled = true; if (resultCode != RESULT_FINISHED) { Log.d(TAG, "Add first fingerprint fail, result:" + resultCode); if (resultCode != RESULT_FINISHED || data == null) { Log.d(TAG, "Add first fingerprint, fail or null data, result:" + resultCode); finish(); return; } mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); if (mToken == null) { Log.w(TAG, "Add first fingerprint, null token"); finish(); return; } mChallenge = data.getLongExtra(EXTRA_KEY_CHALLENGE, -1L); if (mChallenge == -1L) { Log.w(TAG, "Add first fingerprint, invalid challenge"); finish(); return; } mIsEnrolling = false; mHasFirstEnrolled = true; updateAddPreference(); } } Loading Loading @@ -892,7 +925,7 @@ public class FingerprintSettings extends SubSettings { } } private void addFirstFingerprint() { private void addFirstFingerprint(@Nullable Long gkPwHandle) { Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, FeatureFlagUtils.isEnabled(getActivity(), Loading @@ -906,7 +939,13 @@ public class FingerprintSettings extends SubSettings { SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); if (gkPwHandle != null) { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle.longValue()); } else { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge); } startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); } Loading