Merge "Converted Biometric tests to Robolectric tests" into udc-dev

This commit is contained in:
Kevin Liu
2023-04-13 17:22:14 +00:00
committed by Android (Google) Code Review
24 changed files with 343 additions and 61 deletions

View File

@@ -259,6 +259,24 @@ filegroup {
"tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
// Biometric
"tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
"tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt",
"tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt",
"tests/src/com/android/systemui/biometrics/AuthControllerTest.java",
"tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java",
"tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt",
"tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt",
"tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt",
"tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java",
"tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java",
"tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java",
"tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java",
"tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java",
"tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt",
"tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt",
"tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt",
],
path: "tests/src",
}
@@ -403,6 +421,10 @@ android_app {
privileged: true,
resource_dirs: [],
kotlincflags: ["-Xjvm-default=all"],
optimize: {
shrink_resources: false,
proguard_flags_files: ["proguard.flags"],
},
plugins: ["dagger2-compiler"],
}

View File

@@ -144,8 +144,7 @@ constructor(
orientationListener.enable()
}
}
@VisibleForTesting
internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
private val overlayViewParams =
WindowManager.LayoutParams(
@@ -297,7 +296,7 @@ constructor(
}
@VisibleForTesting
internal fun updateOverlayParams(display: Display, bounds: Rect) {
fun updateOverlayParams(display: Display, bounds: Rect) {
val isNaturalOrientation = display.isNaturalOrientation()
val isDefaultOrientation =
if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation

View File

@@ -257,7 +257,7 @@ constructor(
}
@VisibleForTesting
internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
@@ -268,7 +268,7 @@ constructor(
}
@VisibleForTesting
internal suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job {
suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job {
return scope.launch {
alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
showUdfpsBouncer(isVisible)

View File

@@ -13,4 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
sdk=NEWEST_SDK
sdk=NEWEST_SDK
shadows=\
com.android.systemui.testutils.shadow.ShadowLockPatternUtils \
com.android.systemui.testutils.shadow.ShadowTestableLooper

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 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.systemui.testutils.shadow;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import com.android.internal.widget.LockPatternUtils;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(LockPatternUtils.class)
public class ShadowLockPatternUtils {
@Implementation
protected int getCredentialTypeForUser(int userHandle) {
return CREDENTIAL_TYPE_NONE;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 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.systemui.testutils.shadow;
import static org.robolectric.Shadows.shadowOf;
import android.testing.TestableLooper;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@Implements(TestableLooper.class)
public class ShadowTestableLooper {
@RealObject private TestableLooper mRealTestableLooper;
/**
* Process messages in the queue until no more are found.
*/
@Implementation
protected void processAllMessages() {
shadowOf(mRealTestableLooper.getLooper()).idle();
}
}

View File

@@ -18,10 +18,10 @@ package com.android.systemui.biometrics
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -36,7 +36,8 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {

View File

@@ -17,7 +17,7 @@ package com.android.systemui.biometrics
import android.hardware.biometrics.BiometricAuthenticator
import android.os.Bundle
import android.testing.AndroidTestingRunner
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.View
@@ -37,7 +37,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthBiometricFingerprintViewTest : SysuiTestCase() {

View File

@@ -25,7 +25,6 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Handler
import android.os.IBinder
import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
@@ -34,6 +33,7 @@ import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
import android.widget.ScrollView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
@@ -62,7 +62,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthContainerViewTest : SysuiTestCase() {
@@ -481,7 +481,7 @@ class AuthContainerViewTest : SysuiTestCase() {
private fun AuthContainerView.addToView() {
ViewUtils.attachView(this)
waitForIdleSync()
assertThat(isAttachedToWindow).isTrue()
assertThat(isAttachedToWindow()).isTrue()
}
}

View File

@@ -75,7 +75,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -83,6 +82,7 @@ import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -117,7 +117,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@RunWithLooper
@SmallTest
public class AuthControllerTest extends SysuiTestCase {
@@ -265,7 +265,7 @@ public class AuthControllerTest extends SysuiTestCase {
mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
// Ensures that the operations posted on the handler get executed.
mTestableLooper.processAllMessages();
waitForIdleSync();
}
// Callback tests
@@ -285,14 +285,14 @@ public class AuthControllerTest extends SysuiTestCase {
mFpAuthenticatorsRegisteredCaptor.capture());
verify(mFaceManager).addAuthenticatorsRegisteredCallback(
mFaceAuthenticatorsRegisteredCaptor.capture());
mTestableLooper.processAllMessages();
waitForIdleSync();
verify(mFingerprintManager, never()).registerBiometricStateListener(any());
verify(mFaceManager, never()).registerBiometricStateListener(any());
mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
waitForIdleSync();
verify(mFingerprintManager).registerBiometricStateListener(any());
verify(mFaceManager).registerBiometricStateListener(any());
@@ -316,7 +316,7 @@ public class AuthControllerTest extends SysuiTestCase {
// Emulates a device with no authenticators (empty list).
mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
waitForIdleSync();
verify(mFingerprintManager).registerBiometricStateListener(
mBiometricStateCaptor.capture());
@@ -328,7 +328,7 @@ public class AuthControllerTest extends SysuiTestCase {
listener.onEnrollmentsChanged(0 /* userId */,
0xbeef /* sensorId */, true /* hasEnrollments */);
}
mTestableLooper.processAllMessages();
waitForIdleSync();
// Nothing should crash.
}
@@ -692,7 +692,7 @@ public class AuthControllerTest extends SysuiTestCase {
switchTask("other_package");
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mTestableLooper.processAllMessages();
waitForIdleSync();
assertNull(mAuthController.mCurrentDialog);
assertNull(mAuthController.mReceiver);
@@ -709,7 +709,7 @@ public class AuthControllerTest extends SysuiTestCase {
switchTask("other_package");
mAuthController.mTaskStackListener.onTaskStackChanged();
mTestableLooper.processAllMessages();
waitForIdleSync();
assertNull(mAuthController.mCurrentDialog);
assertNull(mAuthController.mReceiver);
@@ -742,7 +742,7 @@ public class AuthControllerTest extends SysuiTestCase {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mAuthController.mBroadcastReceiver.onReceive(mContext, intent);
mTestableLooper.processAllMessages();
waitForIdleSync();
assertNull(mAuthController.mCurrentDialog);
assertNull(mAuthController.mReceiver);
@@ -1021,4 +1021,9 @@ public class AuthControllerTest extends SysuiTestCase {
return dialog;
}
}
@Override
protected void waitForIdleSync() {
mTestableLooper.processAllMessages();
}
}

View File

@@ -32,17 +32,20 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
import android.view.Surface;
import android.view.Surface.Rotation;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,11 +54,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
public class BiometricDisplayListenerTest extends SysuiTestCase {

View File

@@ -16,7 +16,7 @@
package com.android.systemui.biometrics
import android.testing.AndroidTestingRunner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.logging.BiometricMessageDeferralLogger
import com.android.systemui.SysuiTestCase
@@ -33,7 +33,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
class FaceHelpMessageDeferralTest : SysuiTestCase() {
val threshold = .75f
@Mock lateinit var logger: BiometricMessageDeferralLogger

View File

@@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
@@ -49,6 +48,7 @@ import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
import android.view.WindowMetrics
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
@@ -90,7 +90,7 @@ private const val DISPLAY_ID = 2
private const val SENSOR_ID = 1
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class SideFpsControllerTest : SysuiTestCase() {

View File

@@ -25,7 +25,7 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROL
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.testing.AndroidTestingRunner
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -80,7 +80,7 @@ private const val SENSOR_WIDTH = 30
private const val SENSOR_HEIGHT = 60
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {

View File

@@ -58,7 +58,6 @@ import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.VibrationAttributes;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -68,6 +67,7 @@ import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceIdSequence;
@@ -125,7 +125,7 @@ import java.util.Optional;
import javax.inject.Provider;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
public class UdfpsControllerTest extends SysuiTestCase {

View File

@@ -23,8 +23,8 @@ import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.testing.AndroidTestingRunner;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
@Test

View File

@@ -25,9 +25,9 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
public class UdfpsDisplayModeTest extends SysuiTestCase {
private static final int DISPLAY_ID = 0;

View File

@@ -27,10 +27,10 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.shade.ShadeExpansionListener;
@@ -40,7 +40,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {

View File

@@ -17,8 +17,8 @@
package com.android.systemui.biometrics
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.classifier.FalsingCollector
@@ -39,10 +39,9 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.yield
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -54,22 +53,27 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
@TestableLooper.RunWithLooper
@kotlinx.coroutines.ExperimentalCoroutinesApi
class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
@Mock private lateinit var bouncerLogger: TableLogBuffer
private lateinit var testScope: TestScope
@Before
override fun setUp() {
testScope = TestScope()
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
keyguardBouncerRepository =
KeyguardBouncerRepositoryImpl(
mock(com.android.keyguard.ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
testScope.backgroundScope,
bouncerLogger,
)
super.setUp()
@@ -107,7 +111,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
@Test
fun shadeLocked_showAlternateBouncer_unpauseAuth() =
runBlocking(IMMEDIATE) {
testScope.runTest {
// GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing)
mController.onViewAttached()
captureStatusBarStateListeners()
@@ -116,7 +120,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
// WHEN alternate bouncer is requested
val job = mController.listenForAlternateBouncerVisibility(this)
keyguardBouncerRepository.setAlternateVisible(true)
yield()
runCurrent()
// THEN udfps view will animate in & pause auth is updated to NOT pause
verify(mView).animateInUdfpsBouncer(any())
@@ -128,7 +132,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
/** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */
@Test
fun shouldPauseAuthBouncerShowing() =
runBlocking(IMMEDIATE) {
testScope.runTest {
// GIVEN view attached and we're on the keyguard
mController.onViewAttached()
captureStatusBarStateListeners()
@@ -138,15 +142,11 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
val job = mController.listenForBouncerExpansion(this)
keyguardBouncerRepository.setPrimaryShow(true)
keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
yield()
runCurrent()
// THEN UDFPS shouldPauseAuth == true
assertTrue(mController.shouldPauseAuth())
job.cancel()
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
}

View File

@@ -17,9 +17,9 @@
package com.android.systemui.biometrics
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
@@ -39,7 +39,7 @@ import org.mockito.Mockito.`when` as whenEver
import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class UdfpsShellTest : SysuiTestCase() {

View File

@@ -19,7 +19,7 @@ package com.android.systemui.biometrics
import android.graphics.PointF
import android.graphics.RectF
import android.hardware.biometrics.SensorLocationInternal
import android.testing.AndroidTestingRunner
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.LayoutInflater
@@ -49,7 +49,7 @@ private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class UdfpsViewTest : SysuiTestCase() {

View File

@@ -22,12 +22,14 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
import android.testing.DexmakerShareClassLoaderRule;
import android.testing.LeakCheck;
import android.testing.TestWithLooperRule;
import android.testing.TestableLooper;
import android.util.Log;
@@ -73,12 +75,21 @@ public abstract class SysuiTestCase {
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
// set the highest order so it's the innermost rule
@Rule(order = Integer.MAX_VALUE)
public TestWithLooperRule mlooperRule = new TestWithLooperRule();
public TestableDependency mDependency;
private Instrumentation mRealInstrumentation;
private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
@Before
public void SysuiSetup() throws Exception {
// Manually associate a Display to context for Robolectric test. Similar to b/214297409
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
SystemUIInitializer initializer =
SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
initializer.init(true);
@@ -215,6 +226,10 @@ public abstract class SysuiTestCase {
idler.waitForIdle();
}
public static boolean isRobolectricTest() {
return Build.FINGERPRINT.contains("robolectric");
}
private static final void validateThread(Looper l) {
if (Looper.myLooper() == l) {
throw new RuntimeException(

View File

@@ -18,6 +18,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.LeakCheck;
@@ -56,6 +57,11 @@ public class SysuiTestableContext extends TestableContext {
return context;
}
public SysuiTestableContext createDefaultDisplayContext() {
Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
return (SysuiTestableContext) createDisplayContext(display);
}
public void cleanUpReceivers(String testName) {
Set<BroadcastReceiver> copy;
synchronized (mRegisteredReceivers) {

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2023 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 android.testing;
import android.testing.TestableLooper.LooperFrameworkMethod;
import android.testing.TestableLooper.RunWithLooper;
import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.rules.MethodRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/*
* This rule is meant to be an alternative of using AndroidTestingRunner.
* It let tests to start from background thread, and assigns mainLooper or new
* Looper for the Statement.
*/
public class TestWithLooperRule implements MethodRule {
/*
* This rule requires to be the inner most Rule, so the next statement is RunAfters
* instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)'
*/
@Override
public Statement apply(Statement base, FrameworkMethod method, Object target) {
// getting testRunner check, if AndroidTestingRunning then we skip this rule
RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class);
if (runWithAnnotation != null) {
// if AndroidTestingRunner or it's subclass is in use, do nothing
if (AndroidTestingRunner.class.isAssignableFrom(runWithAnnotation.value())) {
return base;
}
}
// check if RunWithLooper annotation is used. If not skip this rule
RunWithLooper looperAnnotation = method.getAnnotation(RunWithLooper.class);
if (looperAnnotation == null) {
looperAnnotation = target.getClass().getAnnotation(RunWithLooper.class);
}
if (looperAnnotation == null) {
return base;
}
try {
wrapMethodInStatement(base, method, target);
} catch (Exception e) {
throw new RuntimeException(e);
}
return base;
}
// This method is based on JUnit4 test runner flow. It might need to be revisited when JUnit is
// upgraded
// TODO(b/277743626): use a cleaner way to wrap each statements; may require some JUnit
// patching to facilitate this.
private void wrapMethodInStatement(Statement base, FrameworkMethod method, Object target)
throws Exception {
Statement next = base;
try {
while (next != null) {
switch (next.getClass().getSimpleName()) {
case "RunAfters":
this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
next.getClass(), "afters", method, target);
next = getNextStatement(next, "next");
break;
case "RunBefores":
this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
next.getClass(), "befores", method, target);
next = getNextStatement(next, "next");
break;
case "FailOnTimeout":
// Note: withPotentialTimeout() from BlockJUnit4ClassRunner might use
// FailOnTimeout which always wraps a new thread during InvokeMethod
// method evaluation.
next = getNextStatement(next, "originalStatement");
break;
case "InvokeMethod":
this.<FrameworkMethod>wrapFieldMethodFor(next,
InvokeMethod.class, "testMethod", method, target);
return;
default:
throw new Exception(
String.format("Unexpected Statement received: [%s]",
next.getClass().getName())
);
}
}
} catch (Exception e) {
throw e;
}
}
// Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod
// within the statement.
private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr,
FrameworkMethod method, Object target)
throws NoSuchFieldException, IllegalAccessException {
Field field = targetClass.getDeclaredField(fieldStr);
field.setAccessible(true);
T fieldInstance = (T) field.get(base);
if (fieldInstance instanceof FrameworkMethod) {
field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance));
} else {
// Befores and afters methods lists
field.set(base, looperWrap(method, target, (List<FrameworkMethod>) fieldInstance));
}
}
// Retrieve the next wrapped statement based on the selected field string
private Statement getNextStatement(Statement base, String fieldStr)
throws NoSuchFieldException, IllegalAccessException {
Field nextField = base.getClass().getDeclaredField(fieldStr);
nextField.setAccessible(true);
Object value = nextField.get(base);
return value instanceof Statement ? (Statement) value : null;
}
protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
FrameworkMethod base) {
RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class);
if (annotation != null) {
return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
}
return base;
}
protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
List<FrameworkMethod> methods) {
RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class);
if (annotation != null) {
methods = new ArrayList<>(methods);
for (int i = 0; i < methods.size(); i++) {
methods.set(i, LooperFrameworkMethod.get(methods.get(i),
annotation.setAsMainLooper(), test));
}
}
return methods;
}
}