diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java index f4997d4abe245..92218b1023c41 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java @@ -25,7 +25,11 @@ import android.os.PowerManager; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * Allows clients (such as keyguard) to register for notifications on when biometric lockout @@ -37,7 +41,8 @@ public class LockoutResetDispatcher implements IBinder.DeathRecipient { private static final String TAG = "LockoutResetTracker"; private final Context mContext; - private final ArrayList mClientCallbacks; + @VisibleForTesting + final List mClientCallbacks = new ArrayList<>(); private static class ClientCallback { private static final long WAKELOCK_TIMEOUT_MS = 2000; @@ -81,7 +86,6 @@ public class LockoutResetDispatcher implements IBinder.DeathRecipient { public LockoutResetDispatcher(Context context) { mContext = context; - mClientCallbacks = new ArrayList<>(); } public void addCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName) { @@ -106,11 +110,13 @@ public class LockoutResetDispatcher implements IBinder.DeathRecipient { @Override public void binderDied(IBinder who) { Slog.e(TAG, "Callback binder died: " + who); - for (ClientCallback callback : mClientCallbacks) { + final Iterator iterator = mClientCallbacks.iterator(); + while (iterator.hasNext()) { + final ClientCallback callback = iterator.next(); if (callback.mCallback.asBinder().equals(who)) { Slog.e(TAG, "Removing dead callback for: " + callback.mOpPackageName); callback.releaseWakelock(); - mClientCallbacks.remove(callback); + iterator.remove(); } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java new file mode 100644 index 0000000000000..a53e22e6e58e0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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.server.biometrics.sensors; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IRemoteCallback; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class LockoutResetDispatcherTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private IBinder mBinder; + @Mock + private IBiometricServiceLockoutResetCallback mCallback; + + private LockoutResetDispatcher mDispatcher; + + @Before + public void setup() { + when(mCallback.asBinder()).thenReturn(mBinder); + mDispatcher = new LockoutResetDispatcher(mContext); + } + + @Test + public void linksToDeath() throws Exception { + mDispatcher.addCallback(mCallback, "package"); + verify(mBinder).linkToDeath(eq(mDispatcher), anyInt()); + } + + @Test + public void notifyLockoutReset() throws Exception { + final int sensorId = 24; + + mDispatcher.addCallback(mCallback, "some.package"); + mDispatcher.notifyLockoutResetCallbacks(sensorId); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(IRemoteCallback.class); + verify(mCallback).onLockoutReset(eq(sensorId), captor.capture()); + captor.getValue().sendResult(new Bundle()); + } + + @Test + public void releaseWakeLockOnDeath() { + mDispatcher.addCallback(mCallback, "a.b.cee"); + mDispatcher.binderDied(mBinder); + + // would be better to check the wake lock + // but this project lacks the extended mockito support to do it + assertThat(mDispatcher.mClientCallbacks).isEmpty(); + } + + @Test + public void releaseCorrectWakeLockOnDeath() { + mDispatcher.addCallback(mCallback, "a.b"); + mDispatcher.binderDied(mock(IBinder.class)); + + assertThat(mDispatcher.mClientCallbacks).hasSize(1); + } +}