Loading core/java/android/security/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -58,3 +58,10 @@ flag { bug: "290312729" is_fixed_read_only: true } flag { name: "report_primary_auth_attempts" namespace: "biometrics" description: "Report primary auth attempts from LockSettingsService" bug: "285053096" } core/java/com/android/internal/widget/ILockSettingsStateListener.aidl 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.internal.widget; /** * Callback interface between LockSettingService and other system services to be notified about the * state of primary authentication (i.e. PIN/pattern/password). * @hide */ oneway interface ILockSettingsStateListener { /** * Defines behavior in response to a successful authentication * @param userId The user Id for the requested authentication */ void onAuthenticationSucceeded(int userId); /** * Defines behavior in response to a failed authentication * @param userId The user Id for the requested authentication */ void onAuthenticationFailed(int userId); } No newline at end of file core/java/com/android/internal/widget/LockSettingsInternal.java +12 −0 Original line number Diff line number Diff line Loading @@ -166,4 +166,16 @@ public abstract class LockSettingsInternal { * Refreshes pending strong auth timeout with the latest admin requirement set by device policy. */ public abstract void refreshStrongAuthTimeout(int userId); /** * Register a LockSettingsStateListener * @param listener The listener to be registered */ public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener); /** * Unregister a LockSettingsStateListener * @param listener The listener to be unregistered */ public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener); } services/core/java/com/android/server/locksettings/LockSettingsService.java +46 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.locksettings; import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; Loading Loading @@ -90,6 +91,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IProgressListener; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; Loading Loading @@ -135,6 +137,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.IWeakEscrowTokenActivatedListener; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; Loading Loading @@ -327,6 +330,9 @@ public class LockSettingsService extends ILockSettings.Stub { private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>(); private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners = new RemoteCallbackList<>(); // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until // the user unlocks the account and credential-encrypted storage is available. Loading Loading @@ -2342,9 +2348,37 @@ public class LockSettingsService extends ILockSettings.Stub { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); } } if (reportPrimaryAuthAttempts()) { final boolean success = response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK; notifyLockSettingsStateListeners(success, userId); } return response; } private void notifyLockSettingsStateListeners(boolean success, int userId) { int i = mLockSettingsStateListeners.beginBroadcast(); try { while (i > 0) { i--; try { if (success) { mLockSettingsStateListeners.getBroadcastItem(i) .onAuthenticationSucceeded(userId); } else { mLockSettingsStateListeners.getBroadcastItem(i) .onAuthenticationFailed(userId); } } catch (RemoteException e) { Slog.e(TAG, "Exception while notifying LockSettingsStateListener:" + " success = " + success + ", userId = " + userId, e); } } } finally { mLockSettingsStateListeners.finishBroadcast(); } } @Override public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential, int userId, @LockPatternUtils.VerifyFlag int flags) { Loading Loading @@ -3662,6 +3696,18 @@ public class LockSettingsService extends ILockSettings.Stub { public void refreshStrongAuthTimeout(int userId) { mStrongAuth.refreshStrongAuthTimeout(userId); } @Override public void registerLockSettingsStateListener( @NonNull ILockSettingsStateListener listener) { mLockSettingsStateListeners.register(listener); } @Override public void unregisterLockSettingsStateListener( @NonNull ILockSettingsStateListener listener) { mLockSettingsStateListeners.unregister(listener); } } private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks { Loading services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +70 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; Loading @@ -30,25 +32,30 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -59,6 +66,7 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { Loading Loading @@ -398,6 +406,60 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); } @Test public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); } @Test public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final LockscreenCredential badPassword = newPassword("badPassword"); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationFailed(PRIMARY_USER_ID); } @Test public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final LockscreenCredential badPassword = newPassword("badPassword"); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); mLocalService.unregisterLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID); } @Test public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() { setUserSetupComplete(false); Loading Loading @@ -537,4 +599,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertNotEquals(0, mGateKeeperService.getSecureUserId(userId)); } } private ILockSettingsStateListener mockLockSettingsStateListener() { ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class); IBinder binder = mock(IBinder.class); when(binder.isBinderAlive()).thenReturn(true); when(listener.asBinder()).thenReturn(binder); return listener; } } Loading
core/java/android/security/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -58,3 +58,10 @@ flag { bug: "290312729" is_fixed_read_only: true } flag { name: "report_primary_auth_attempts" namespace: "biometrics" description: "Report primary auth attempts from LockSettingsService" bug: "285053096" }
core/java/com/android/internal/widget/ILockSettingsStateListener.aidl 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.internal.widget; /** * Callback interface between LockSettingService and other system services to be notified about the * state of primary authentication (i.e. PIN/pattern/password). * @hide */ oneway interface ILockSettingsStateListener { /** * Defines behavior in response to a successful authentication * @param userId The user Id for the requested authentication */ void onAuthenticationSucceeded(int userId); /** * Defines behavior in response to a failed authentication * @param userId The user Id for the requested authentication */ void onAuthenticationFailed(int userId); } No newline at end of file
core/java/com/android/internal/widget/LockSettingsInternal.java +12 −0 Original line number Diff line number Diff line Loading @@ -166,4 +166,16 @@ public abstract class LockSettingsInternal { * Refreshes pending strong auth timeout with the latest admin requirement set by device policy. */ public abstract void refreshStrongAuthTimeout(int userId); /** * Register a LockSettingsStateListener * @param listener The listener to be registered */ public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener); /** * Unregister a LockSettingsStateListener * @param listener The listener to be unregistered */ public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener); }
services/core/java/com/android/server/locksettings/LockSettingsService.java +46 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.locksettings; import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; Loading Loading @@ -90,6 +91,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IProgressListener; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; Loading Loading @@ -135,6 +137,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.IWeakEscrowTokenActivatedListener; import com.android.internal.widget.IWeakEscrowTokenRemovedListener; import com.android.internal.widget.LockPatternUtils; Loading Loading @@ -327,6 +330,9 @@ public class LockSettingsService extends ILockSettings.Stub { private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>(); private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners = new RemoteCallbackList<>(); // This class manages life cycle events for encrypted users on File Based Encryption (FBE) // devices. The most basic of these is to show/hide notifications about missing features until // the user unlocks the account and credential-encrypted storage is available. Loading Loading @@ -2342,9 +2348,37 @@ public class LockSettingsService extends ILockSettings.Stub { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); } } if (reportPrimaryAuthAttempts()) { final boolean success = response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK; notifyLockSettingsStateListeners(success, userId); } return response; } private void notifyLockSettingsStateListeners(boolean success, int userId) { int i = mLockSettingsStateListeners.beginBroadcast(); try { while (i > 0) { i--; try { if (success) { mLockSettingsStateListeners.getBroadcastItem(i) .onAuthenticationSucceeded(userId); } else { mLockSettingsStateListeners.getBroadcastItem(i) .onAuthenticationFailed(userId); } } catch (RemoteException e) { Slog.e(TAG, "Exception while notifying LockSettingsStateListener:" + " success = " + success + ", userId = " + userId, e); } } } finally { mLockSettingsStateListeners.finishBroadcast(); } } @Override public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential, int userId, @LockPatternUtils.VerifyFlag int flags) { Loading Loading @@ -3662,6 +3696,18 @@ public class LockSettingsService extends ILockSettings.Stub { public void refreshStrongAuthTimeout(int userId) { mStrongAuth.refreshStrongAuthTimeout(userId); } @Override public void registerLockSettingsStateListener( @NonNull ILockSettingsStateListener listener) { mLockSettingsStateListeners.register(listener); } @Override public void unregisterLockSettingsStateListener( @NonNull ILockSettingsStateListener listener) { mLockSettingsStateListeners.unregister(listener); } } private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks { Loading
services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +70 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; Loading @@ -30,25 +32,30 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.ILockSettingsStateListener; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; Loading @@ -59,6 +66,7 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { Loading Loading @@ -398,6 +406,60 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); } @Test public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); } @Test public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final LockscreenCredential badPassword = newPassword("badPassword"); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationFailed(PRIMARY_USER_ID); } @Test public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception { mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); final LockscreenCredential password = newPassword("password"); setCredential(PRIMARY_USER_ID, password); final LockscreenCredential badPassword = newPassword("badPassword"); final ILockSettingsStateListener listener = mockLockSettingsStateListener(); mLocalService.registerLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID); mLocalService.unregisterLockSettingsStateListener(listener); assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */) .getResponseCode()); verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID); } @Test public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() { setUserSetupComplete(false); Loading Loading @@ -537,4 +599,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertNotEquals(0, mGateKeeperService.getSecureUserId(userId)); } } private ILockSettingsStateListener mockLockSettingsStateListener() { ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class); IBinder binder = mock(IBinder.class); when(binder.isBinderAlive()).thenReturn(true); when(listener.asBinder()).thenReturn(binder); return listener; } }