diff options
Diffstat (limited to 'tests/src/com/android/launcher3/util')
20 files changed, 484 insertions, 269 deletions
diff --git a/tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt b/tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt new file mode 100644 index 0000000000..4770546e51 --- /dev/null +++ b/tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt @@ -0,0 +1,148 @@ +/* + * 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.launcher3.util + +import android.content.Context +import android.content.res.Configuration +import android.util.DisplayMetrics +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CellContentDimensionsTest { + private var context: Context? = null + private val runningContext: Context = ApplicationProvider.getApplicationContext() + private lateinit var iconSizeSteps: IconSizeSteps + + @Before + fun setup() { + // 160dp makes 1px = 1dp + val config = + Configuration(runningContext.resources.configuration).apply { + this.densityDpi = DisplayMetrics.DENSITY_DEFAULT + } + context = runningContext.createConfigurationContext(config) + iconSizeSteps = IconSizeSteps(context!!.resources) + } + + @Test + fun dimensionsFitTheCell() { + val cellSize = Pair(80, 104) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(93) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(66) + assertThat(iconDrawablePaddingPx).isEqualTo(8) + assertThat(iconTextSizePx).isEqualTo(14) + } + } + + @Test + fun decreasePadding() { + val cellSize = Pair(67, 87) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(87) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(66) + assertThat(iconDrawablePaddingPx).isEqualTo(2) + assertThat(iconTextSizePx).isEqualTo(14) + } + } + + @Test + fun decreaseIcon() { + val cellSize = Pair(65, 84) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(82) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(63) + assertThat(iconDrawablePaddingPx).isEqualTo(0) + assertThat(iconTextSizePx).isEqualTo(14) + } + } + + @Test + fun decreaseText() { + val cellSize = Pair(63, 81) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(81) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(63) + assertThat(iconDrawablePaddingPx).isEqualTo(0) + assertThat(iconTextSizePx).isEqualTo(13) + } + } + + @Test + fun decreaseIconAndTextTwoSteps() { + val cellSize = Pair(60, 78) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(77) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(61) + assertThat(iconDrawablePaddingPx).isEqualTo(0) + assertThat(iconTextSizePx).isEqualTo(12) + } + } + + @Test + fun decreaseIconAndTextToMinimum() { + val cellSize = Pair(52, 63) + val cellContentDimensions = + CellContentDimensions(iconSizePx = 66, iconDrawablePaddingPx = 8, iconTextSizePx = 14) + + val contentHeight = + cellContentDimensions.resizeToFitCellHeight(cellSize.second, iconSizeSteps) + + assertThat(contentHeight).isEqualTo(63) + cellContentDimensions.run { + assertThat(iconSizePx).isEqualTo(52) + assertThat(iconDrawablePaddingPx).isEqualTo(0) + assertThat(iconTextSizePx).isEqualTo(8) + } + } +} diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt index 8e4e99812e..8670d40f4e 100644 --- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt +++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt @@ -30,8 +30,10 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING import com.android.launcher3.util.DisplayController.CHANGE_DENSITY import com.android.launcher3.util.DisplayController.CHANGE_ROTATION +import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext import com.android.launcher3.util.window.CachedDisplayInfo @@ -40,11 +42,13 @@ import kotlin.math.min import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import org.mockito.stubbing.Answer /** Unit tests for {@link DisplayController} */ @@ -54,13 +58,13 @@ class DisplayControllerTest { private val appContext: Context = ApplicationProvider.getApplicationContext() - @Mock private lateinit var context: SandboxContext - @Mock private lateinit var windowManagerProxy: WindowManagerProxy - @Mock private lateinit var launcherPrefs: LauncherPrefs - @Mock private lateinit var displayManager: DisplayManager - @Mock private lateinit var display: Display - @Mock private lateinit var resources: Resources - @Mock private lateinit var displayInfoChangeListener: DisplayInfoChangeListener + private val context: SandboxContext = mock() + private val windowManagerProxy: WindowManagerProxy = mock() + private val launcherPrefs: LauncherPrefs = mock() + private val displayManager: DisplayManager = mock() + private val display: Display = mock() + private val resources: Resources = mock() + private val displayInfoChangeListener: DisplayInfoChangeListener = mock() private lateinit var displayController: DisplayController @@ -86,9 +90,9 @@ class DisplayControllerTest { @Before fun setUp() { - MockitoAnnotations.initMocks(this) whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy) whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs) + whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false) // Mock WindowManagerProxy val displayInfo = @@ -107,11 +111,12 @@ class DisplayControllerTest { bounds[i.getArgument<CachedDisplayInfo>(1).rotation] } + whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON) // Mock context - whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context) + whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context) whenever(context.getSystemService(eq(DisplayManager::class.java))) .thenReturn(displayManager) - doNothing().`when`(context).registerComponentCallbacks(any()) + doNothing().whenever(context).registerComponentCallbacks(any()) // Mock display whenever(display.rotation).thenReturn(displayInfo.rotation) @@ -156,4 +161,13 @@ class DisplayControllerTest { verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_DENSITY)) } + + @Test + @UiThreadTest + fun testTaskbarPinning() { + whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true) + displayController.handleInfoChange(display) + verify(displayInfoChangeListener) + .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING)) + } } diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt deleted file mode 100644 index c9c9616fb9..0000000000 --- a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util - -/** - * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects - * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not - * be null"). To fix this, we can use methods that modify the return type to be nullable. This - * causes Kotlin to skip the null checks. - */ -import org.mockito.ArgumentCaptor -import org.mockito.Mockito - -/** - * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when null is - * returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> eq(obj: T): T = Mockito.eq<T>(obj) - -/** - * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when null is - * returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> same(obj: T): T = Mockito.same<T>(obj) - -/** - * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is - * returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> any(type: Class<T>): T = Mockito.any<T>(type) - -inline fun <reified T> any(): T = any(T::class.java) - -/** Kotlin type-inferred version of Mockito.nullable() */ -inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java) - -/** - * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException when - * null is returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() - -/** - * Helper function for creating an argumentCaptor in kotlin. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = - ArgumentCaptor.forClass(T::class.java) - -/** - * Helper function for creating new mocks, without the need to pass in a [Class] instance. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java) - -/** - * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when - * kotlin tests are mocking kotlin objects and the methods take non-null parameters: - * ``` - * java.lang.NullPointerException: capture() must not be null - * ``` - */ -class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) { - private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz) - fun capture(): T = wrapped.capture() - val value: T - get() = wrapped.value -} - -/** - * Helper function for creating an argumentCaptor in kotlin. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = - KotlinArgumentCaptor(T::class.java) - -/** - * Helper function for creating and using a single-use ArgumentCaptor in kotlin. - * - * ``` - * val captor = argumentCaptor<Foo>() - * verify(...).someMethod(captor.capture()) - * val captured = captor.value - * ``` - * - * becomes: - * ``` - * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) } - * ``` - * - * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException. - */ -inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T = - kotlinArgumentCaptor<T>().apply { block() }.value diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java index 261436b3ac..244dc269b6 100644 --- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -84,6 +84,17 @@ public class LauncherModelHelper { public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2"; public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3"; public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4"; + public static final String TEST_ACTIVITY4 = "com.android.launcher3.tests.Activity5"; + public static final String TEST_ACTIVITY5 = "com.android.launcher3.tests.Activity6"; + public static final String TEST_ACTIVITY6 = "com.android.launcher3.tests.Activity7"; + public static final String TEST_ACTIVITY7 = "com.android.launcher3.tests.Activity8"; + public static final String TEST_ACTIVITY8 = "com.android.launcher3.tests.Activity9"; + public static final String TEST_ACTIVITY9 = "com.android.launcher3.tests.Activity10"; + public static final String TEST_ACTIVITY10 = "com.android.launcher3.tests.Activity11"; + public static final String TEST_ACTIVITY11 = "com.android.launcher3.tests.Activity12"; + public static final String TEST_ACTIVITY12 = "com.android.launcher3.tests.Activity13"; + public static final String TEST_ACTIVITY13 = "com.android.launcher3.tests.Activity14"; + public static final String TEST_ACTIVITY14 = "com.android.launcher3.tests.Activity15"; // Authority for providing a test default-workspace-layout data. private static final String TEST_PROVIDER_AUTHORITY = diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt index 92ab2cb90b..2c4a54f9d8 100644 --- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt +++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt @@ -26,29 +26,27 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever /** Unit tests for {@link LockedUserState} */ @SmallTest @RunWith(AndroidJUnit4::class) class LockedUserStateTest { - @Mock lateinit var userManager: UserManager - @Mock lateinit var context: Context + private val userManager: UserManager = mock() + private val context: Context = mock() @Before fun setup() { - MockitoAnnotations.initMocks(this) - `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager) + whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager) } @Test fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() { - `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) + whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) val action: Runnable = mock() LockedUserState(context).runOnUserUnlocked(action) verify(action).run() @@ -56,7 +54,7 @@ class LockedUserStateTest { @Test fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() { - `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) + whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) val action: Runnable = mock() val state = LockedUserState(context) state.runOnUserUnlocked(action) @@ -67,13 +65,13 @@ class LockedUserStateTest { @Test fun isUserUnlocked_returns_true_when_user_is_unlocked() { - `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) + whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true) assertThat(LockedUserState(context).isUserUnlocked).isTrue() } @Test fun isUserUnlocked_returns_false_when_user_is_locked() { - `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) + whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false) assertThat(LockedUserState(context).isUserUnlocked).isFalse() } } diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt new file mode 100644 index 0000000000..61ec6692ee --- /dev/null +++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt @@ -0,0 +1,30 @@ +package com.android.launcher3.util + +import com.android.launcher3.LauncherModel +import com.android.launcher3.model.BgDataModel + +object ModelTestExtensions { + /** Clears and reloads Launcher db to cleanup the workspace */ + fun LauncherModel.clearModelDb() { + // Load the model once so that there is no pending migration: + loadModelSync() + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + modelDbController.run { + tryMigrateDB() + createEmptyDB() + clearEmptyDbFlag() + } + } + // Reload model + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { forceReload() } + loadModelSync() + } + + fun LauncherModel.loadModelSync() { + val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {} + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) } + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {} + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {} + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) } + } +} diff --git a/tests/src/com/android/launcher3/util/TestConstants.java b/tests/src/com/android/launcher3/util/TestConstants.java new file mode 100644 index 0000000000..6f3c63ae02 --- /dev/null +++ b/tests/src/com/android/launcher3/util/TestConstants.java @@ -0,0 +1,29 @@ +/* + * 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.launcher3.util; + +public class TestConstants { + public static class AppNames { + + public static final String TEST_APP_NAME = "LauncherTestApp"; + public static final String DUMMY_APP_NAME = "Aardwolf"; + public static final String MAPS_APP_NAME = "Maps"; + public static final String STORE_APP_NAME = "Play Store"; + public static final String GMAIL_APP_NAME = "Gmail"; + public static final String CHROME_APP_NAME = "Chrome"; + public static final String MESSAGES_APP_NAME = "Messages"; + } +} diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt index cf80ece740..b4d3ba853a 100644 --- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt +++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt @@ -32,6 +32,8 @@ class TestResourceHelper(private val context: Context, specsFileId: Int) : styleId.contentEquals(R.styleable.WorkspaceSpec) -> TestR.styleable.WorkspaceSpec styleId.contentEquals(R.styleable.FolderSpec) -> TestR.styleable.FolderSpec styleId.contentEquals(R.styleable.AllAppsSpec) -> TestR.styleable.AllAppsSpec + styleId.contentEquals(R.styleable.ResponsiveSpecGroup) -> + TestR.styleable.ResponsiveSpecGroup else -> styleId.clone() } diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java index 21059e6075..683f3238a0 100644 --- a/tests/src/com/android/launcher3/util/TestUtil.java +++ b/tests/src/com/android/launcher3/util/TestUtil.java @@ -24,12 +24,15 @@ import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY; import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL; import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG; +import static org.junit.Assert.assertTrue; + import android.app.Instrumentation; import android.app.blob.BlobHandle; import android.app.blob.BlobStoreManager; import android.content.Context; import android.content.pm.LauncherApps; import android.content.res.Resources; +import android.graphics.Point; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; @@ -46,6 +49,9 @@ import androidx.test.uiautomator.UiDevice; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags.BooleanFlag; import com.android.launcher3.config.FeatureFlags.IntFlag; +import com.android.launcher3.tapl.LauncherInstrumentation; +import com.android.launcher3.tapl.Workspace; +import com.android.launcher3.util.rule.TestStabilityRule; import org.junit.Assert; @@ -125,6 +131,28 @@ public class TestUtil { } /** + * @return Grid coordinates from the center and corners of the Workspace. Those are not pixels. + * See {@link Workspace#getIconGridDimensions()} + */ + public static Point[] getCornersAndCenterPositions(LauncherInstrumentation launcher) { + final Point dimensions = launcher.getWorkspace().getIconGridDimensions(); + if (TestStabilityRule.isPresubmit()) { + // Return only center in presubmit to fit under the presubmit SLO. + return new Point[]{ + new Point(dimensions.x / 2, dimensions.y / 2) + }; + } else { + return new Point[]{ + new Point(0, 1), + new Point(0, dimensions.y - 2), + new Point(dimensions.x - 1, 1), + new Point(dimensions.x - 1, dimensions.y - 2), + new Point(dimensions.x / 2, dimensions.y / 2) + }; + } + } + + /** * Utility class to override a boolean flag during test. Note that the returned SafeCloseable * must be closed to restore the original state */ @@ -226,6 +254,17 @@ public class TestUtil { } } + // Please don't add negative test cases for methods that fail only after a long wait. + public static void expectFail(String message, Runnable action) { + boolean failed = false; + try { + action.run(); + } catch (AssertionError e) { + failed = true; + } + assertTrue(message, failed); + } + /** Interface to indicate a runnable which can throw any exception. */ public interface UncheckedRunnable { /** Method to run the task */ diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 62d70ad1cd..10b428a111 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -59,8 +59,9 @@ public class FailureWatcher extends TestWatcher { throw new AssertionError( "Launcher received events not sent by the test. This may mean " + "that the touch screen of the lab device has sent false" - + " events. See the logcat for TaplEvents tag and look " - + "for events with deviceId != -1"); + + " events. See the logcat for " + + "TaplEvents|LauncherEvents|TaplTarget tag and look for " + + "events with deviceId != -1"); } } } diff --git a/tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt b/tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt new file mode 100644 index 0000000000..d6824561c1 --- /dev/null +++ b/tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2008 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.launcher3.util.rule + +import android.platform.test.flag.junit.SetFlagsRule + +fun SetFlagsRule.setFlags(enabled: Boolean, vararg flagName: String) { + if (enabled) enableFlags(*flagName) else disableFlags(*flagName) +} diff --git a/tests/src/com/android/launcher3/util/rule/StaticMockitoRule.java b/tests/src/com/android/launcher3/util/rule/StaticMockitoRule.java new file mode 100644 index 0000000000..6b91474f02 --- /dev/null +++ b/tests/src/com/android/launcher3/util/rule/StaticMockitoRule.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util.rule; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; + +import org.junit.rules.MethodRule; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; +import org.mockito.junit.MockitoRule; +import org.mockito.quality.Strictness; + +/** + * Similar to {@link MockitoRule}, but uses {@link StaticMockitoSession}, which allows mocking + * static methods. + */ +public class StaticMockitoRule implements MethodRule { + private Class<?>[] mClasses; + + public StaticMockitoRule(Class<?>... classes) { + mClasses = classes; + } + + @Override + public Statement apply(Statement base, FrameworkMethod method, Object target) { + return new Statement() { + public void evaluate() throws Throwable { + StaticMockitoSessionBuilder builder = + mockitoSession() + .name(target.getClass().getSimpleName() + "." + method.getName()) + .initMocks(target) + .strictness(Strictness.STRICT_STUBS); + + for (Class<?> clazz : mClasses) { + builder.mockStatic(clazz); + } + + StaticMockitoSession session = builder.startMocking(); + Throwable testFailure = evaluateSafely(base); + session.finishMocking(testFailure); + if (testFailure != null) { + throw testFailure; + } + } + + private Throwable evaluateSafely(Statement base) { + try { + base.evaluate(); + return null; + } catch (Throwable throwable) { + return throwable; + } + } + }; + } +} diff --git a/tests/src/com/android/launcher3/util/rule/TISBindRule.java b/tests/src/com/android/launcher3/util/rule/TISBindRule.java deleted file mode 100644 index 3ec4a29cdf..0000000000 --- a/tests/src/com/android/launcher3/util/rule/TISBindRule.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.launcher3.util.rule; - -import android.app.UiAutomation; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -public class TISBindRule implements TestRule { - public static String TAG = "TISBindRule"; - public static String INTENT_FILTER = "android.intent.action.QUICKSTEP_SERVICE"; - public static String TIS_PERMISSIONS = "android.permission.STATUS_BAR_SERVICE"; - - private String getLauncherPackageName(Context context) { - return ComponentName.unflattenFromString(context.getString( - com.android.internal.R.string.config_recentsComponentName)).getPackageName(); - } - - private ServiceConnection createConnection() { - return new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - Log.d(TAG, "Connected to TouchInteractionService"); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.d(TAG, "Disconnected from TouchInteractionService"); - } - }; - } - - @NonNull - @Override - public Statement apply(@NonNull Statement base, @NonNull Description description) { - return new Statement() { - - @Override - public void evaluate() throws Throwable { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - final ServiceConnection connection = createConnection(); - UiAutomation uiAutomation = - InstrumentationRegistry.getInstrumentation().getUiAutomation(); - uiAutomation.adoptShellPermissionIdentity(TIS_PERMISSIONS); - Intent launchIntent = new Intent(INTENT_FILTER); - launchIntent.setPackage(getLauncherPackageName(context)); - context.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE); - uiAutomation.dropShellPermissionIdentity(); - try { - base.evaluate(); - } finally { - context.unbindService(connection); - } - } - }; - } -} diff --git a/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java new file mode 100644 index 0000000000..2b45902813 --- /dev/null +++ b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java @@ -0,0 +1,55 @@ +/* + * 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.launcher3.util.rule; + +import androidx.annotation.NonNull; +import androidx.test.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; + +import com.android.launcher3.tapl.LauncherInstrumentation; +import com.android.launcher3.ui.AbstractLauncherUiTest; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Isolates tests from some of the state created by the previous test. + */ +public class TestIsolationRule implements TestRule { + private final LauncherInstrumentation mLauncher; + private final boolean mRequireOneActiveActivity; + + public TestIsolationRule(LauncherInstrumentation launcher, boolean requireOneActiveActivity) { + mLauncher = launcher; + mRequireOneActiveActivity = requireOneActiveActivity; + } + + @NonNull + @Override + public Statement apply(@NonNull Statement base, @NonNull Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + // Make sure that Launcher workspace looks correct. + + UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressHome(); + AbstractLauncherUiTest.checkDetectedLeaks(mLauncher, mRequireOneActiveActivity); + } + }; + } +} diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java index 38de07192a..b51045fc09 100644 --- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java +++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java @@ -146,4 +146,8 @@ public class TestStabilityRule implements TestRule { return sRunFlavor; } + + public static boolean isPresubmit() { + return getRunFlavor() == PLATFORM_PRESUBMIT; + } } diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt index ccbae4fb0c..e70ea18f7a 100644 --- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt +++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt @@ -132,7 +132,9 @@ class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : Te for (i in 0 until viewCaptureData!!.windowDataCount) { frameCount += viewCaptureData!!.getWindowData(i).frameDataCount } - assertTrue("Empty ViewCapture data", frameCount > 0) + + val mayProduceNoFrames = description.getAnnotation(MayProduceNoFrames::class.java) != null + assertTrue("Empty ViewCapture data", mayProduceNoFrames || frameCount > 0) val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData) if (!anomalies.isEmpty()) { @@ -159,4 +161,8 @@ class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : Te ) } } + + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.FUNCTION) + annotation class MayProduceNoFrames } diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java index 4b65439db5..dfccffc1f5 100644 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java +++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java @@ -33,17 +33,17 @@ final class AlphaJumpDetector extends AnomalyDetector { private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( CONTENT - + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge", CONTENT - + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" + "|WidgetCellPreview:id/widget_preview_container|WidgetCell$1|FrameLayout" + "|ImageView:id/icon", - CONTENT + "AddItemDragLayer:id/add_item_drag_layer|View", + CONTENT + "SimpleDragLayer:id/add_item_drag_layer|View", DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageButton:id/widget_reconfigure_button", DRAG_LAYER @@ -62,18 +62,6 @@ final class AlphaJumpDetector extends AnomalyDetector { + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id" + "/select_mode_buttons|ImageButton:id/close", DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id" - + "/action_buttons|Button:id/action_screenshot", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id" - + "/action_buttons|Button:id/action_select", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id" - + "/action_buttons|Button:id/action_split", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id" - + "/action_buttons|Space:id/action_split_space", - DRAG_LAYER + "PopupContainerWithArrow:id/popup_container|LinearLayout:id" + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material" + "|DeepShortcutTextView:id/bubble_text", @@ -116,23 +104,14 @@ final class AlphaJumpDetector extends AnomalyDetector { RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel", DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_screenshot", + + "|LinearLayout:id/action_buttons", RECENTS_DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_screenshot", + + "|LinearLayout:id/action_buttons", + DRAG_LAYER + "IconView", DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_select", - RECENTS_DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_select", - DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_split", - RECENTS_DRAG_LAYER - + "NexusOverviewActionsView:id/overview_actions_view" - + "|LinearLayout:id/action_buttons|Button:id/action_split", - DRAG_LAYER + "IconView" + + "OptionsPopupView:id/popup_container|DeepShortcutView:id/system_shortcut" + + "|BubbleTextView:id/bubble_text" )); // Minimal increase or decrease of view's alpha between frames that triggers the error. diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java index 8b88ace181..fc8f818bf5 100644 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java +++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java @@ -38,22 +38,25 @@ final class FlashDetector extends AnomalyDetector { private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( CONTENT + "LauncherRootView:id/launcher|FloatingIconView", - DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView", + DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView", + DRAG_LAYER + "LauncherRecentsView:id/overview_panel|ClearAllButton:id/clear_all", DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id" + "/apps_list_view|BubbleTextView:id/icon", CONTENT - + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" + "|WidgetCellPreview:id/widget_preview_container|WidgetImageView:id" + "/widget_preview", CONTENT - + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content" + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell" + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge", - RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon", + RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView", + RECENTS_DRAG_LAYER + + "FallbackRecentsView:id/overview_panel|ClearAllButton:id/clear_all", DRAG_LAYER + "SearchContainerView:id/apps_view", DRAG_LAYER + "LauncherDragView", DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail", @@ -64,7 +67,8 @@ final class FlashDetector extends AnomalyDetector { + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id" + "/linear_layout_container|FrameLayout:id/recycler_view_container" + "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id" - + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header" + + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header", + DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view" )); // Per-AnalysisNode data that's specific to this detector. diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java index a1ddcb054e..88ace68d53 100644 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java +++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java @@ -41,12 +41,12 @@ final class PositionJumpDetector extends AnomalyDetector { DRAG_LAYER + "AppWidgetResizeFrame", DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view", CONTENT - + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + + "SimpleDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content", DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "LauncherDragView", - RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView", + RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel", CONTENT + "LauncherRootView:id/launcher|FloatingIconView", DRAG_LAYER + "FloatingTaskView", DRAG_LAYER + "LauncherRecentsView:id/overview_panel" diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java index 9459cc2d13..b27ccbfd2f 100644 --- a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java +++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java @@ -35,8 +35,8 @@ public class ViewCaptureAnalyzer { // All detectors. They will be invoked in the order listed here. private static final AnomalyDetector[] ANOMALY_DETECTORS = { - new AlphaJumpDetector(), - new FlashDetector(), +// new AlphaJumpDetector(), // b/309014345 +// new FlashDetector(), // b/309014345 new PositionJumpDetector() }; |