diff options
24 files changed, 343 insertions, 61 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7a1d9a3ad025..9d32e905d85d 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -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"], } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index eb5d23a23abb..4319f01bed14 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt index 5101ad4c94bc..3b50bbcd9251 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -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) diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties index 2a75bd98bfe8..438d54c5a200 100644 --- a/packages/SystemUI/tests/robolectric/config/robolectric.properties +++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties @@ -13,4 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -sdk=NEWEST_SDK
\ No newline at end of file +sdk=NEWEST_SDK +shadows=\ + com.android.systemui.testutils.shadow.ShadowLockPatternUtils \ + com.android.systemui.testutils.shadow.ShadowTestableLooper
\ No newline at end of file diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java new file mode 100644 index 000000000000..b248ce356394 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java @@ -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; + } +} diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java new file mode 100644 index 000000000000..a9b8bc044554 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java @@ -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(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt index 58d906907488..dd87f6e7f64b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt index bce98cf116d4..9f789e4cbd18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index f914e758ecdb..6d4c467aca7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -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() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index c068efb1b5d4..0f20ace49a47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -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(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java index 69c7f364d235..24a13a57dae7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java @@ -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 { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt index c9ccdb36da89..88b6c39531e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index 33345b5b9f75..c554af630106 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index b2c2ae7458ae..1faad8084535 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index eae95a54744b..8d8b19050e4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -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 { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index 78fb5b00a21e..cd9189bef7f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java index 1bc237d422d6..5239966f1923 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java @@ -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; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 6d9acb92a5f4..af3a06b74943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -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 { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index b848413423d1..fea9d2d5a6be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -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 - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt index c2a129be66a4..8b374ae54127 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 07b4a649a604..f0759670a21b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -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() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 1bab99787de4..1ec4e8c48707 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -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( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 0674ea855d7f..5ff57aad9f5d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -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) { diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java new file mode 100644 index 000000000000..99b303e0c43a --- /dev/null +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -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; + } +} |