diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2021-08-27 21:22:17 +0000 |
---|---|---|
committer | Alex Chau <alexchau@google.com> | 2021-08-31 10:51:52 +0000 |
commit | 777d49062ff241ad2d0cfa7d1b0cf01368d32b51 (patch) | |
tree | 5f6e1d84e61b438202402228dd6e6bfa7545716a | |
parent | ee3814de1acbdc994e4d0862969019d5ee4f632f (diff) |
Revert "Revert "Migrating all model tests to Instrumentation tests""
This reverts commit 7a4a30d86d471e6c45adc2a9907efb27e9b1799b.
Test: Presubmit
Reason for revert: Fixing original bug
Bug: 196825541
Change-Id: Id4b1eb24a89564d264266d305aebea52917dfcd9
-rw-r--r-- | quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java (renamed from quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java) | 128 | ||||
-rw-r--r-- | robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java | 125 | ||||
-rw-r--r-- | robolectric_tests/src/com/android/launcher3/testing/TestActivity.java | 46 | ||||
-rw-r--r-- | robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java | 101 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherAppState.java | 5 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherModel.java | 7 | ||||
-rw-r--r-- | src/com/android/launcher3/graphics/LauncherPreviewRenderer.java | 50 | ||||
-rw-r--r-- | src/com/android/launcher3/icons/IconCache.java | 3 | ||||
-rw-r--r-- | src/com/android/launcher3/util/DisplayController.java | 17 | ||||
-rw-r--r-- | src/com/android/launcher3/util/MainThreadInitializedObject.java | 90 | ||||
-rw-r--r-- | src/com/android/launcher3/util/SettingsCache.java | 8 | ||||
-rw-r--r-- | src/com/android/launcher3/util/UiThreadHelper.java | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/widget/custom/CustomWidgetManager.java | 6 | ||||
-rw-r--r-- | tests/res/raw/cache_data_updated_task_data.txt (renamed from robolectric_tests/resources/cache_data_updated_task_data.txt) | 0 | ||||
-rw-r--r-- | tests/res/raw/db_schema_v10.json (renamed from robolectric_tests/resources/db_schema_v10.json) | 0 | ||||
-rw-r--r-- | tests/res/raw/package_install_state_change_task_data.txt (renamed from robolectric_tests/resources/package_install_state_change_task_data.txt) | 0 | ||||
-rw-r--r-- | tests/res/raw/widgets_predication_update_task_data.txt (renamed from robolectric_tests/resources/widgets_predication_update_task_data.txt) | 0 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java) | 44 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/BackupRestoreTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java) | 111 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java) | 20 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java) | 20 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java) | 45 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java (renamed from robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java) | 69 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/LoaderCursorTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java) | 32 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java) | 119 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java (renamed from robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java) | 20 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java (renamed from robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java) | 11 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java | 53 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java (renamed from robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java) | 0 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/util/LauncherModelHelper.java (renamed from robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java) | 299 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/util/ReflectionHelpers.java | 58 |
31 files changed, 807 insertions, 684 deletions
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java index f82fbcc15b..c1b3beb475 100644 --- a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java +++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java @@ -15,24 +15,31 @@ */ package com.android.launcher3.model; +import static android.os.Process.myUserHandle; + import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.robolectric.Shadows.shadowOf; +import static org.mockito.Mockito.doReturn; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetId; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; -import android.content.Context; -import android.os.Process; import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; @@ -40,38 +47,34 @@ import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.QuickstepModelDelegate.PredictorState; -import com.android.launcher3.shadows.ShadowDeviceFlag; import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowAppWidgetManager; -import org.robolectric.shadows.ShadowPackageManager; -import org.robolectric.util.ReflectionHelpers; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -@RunWith(RobolectricTestRunner.class) +@SmallTest +@RunWith(AndroidJUnit4.class) public final class WidgetsPredicationUpdateTaskTest { - private AppWidgetProviderInfo mApp1Provider1 = new AppWidgetProviderInfo(); - private AppWidgetProviderInfo mApp1Provider2 = new AppWidgetProviderInfo(); - private AppWidgetProviderInfo mApp2Provider1 = new AppWidgetProviderInfo(); - private AppWidgetProviderInfo mApp4Provider1 = new AppWidgetProviderInfo(); - private AppWidgetProviderInfo mApp4Provider2 = new AppWidgetProviderInfo(); - private AppWidgetProviderInfo mApp5Provider1 = new AppWidgetProviderInfo(); + private AppWidgetProviderInfo mApp1Provider1; + private AppWidgetProviderInfo mApp1Provider2; + private AppWidgetProviderInfo mApp2Provider1; + private AppWidgetProviderInfo mApp4Provider1; + private AppWidgetProviderInfo mApp4Provider2; + private AppWidgetProviderInfo mApp5Provider1; + private List<AppWidgetProviderInfo> allWidgets; private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback(); - private Context mContext; private LauncherModelHelper mModelHelper; private UserHandle mUserHandle; @@ -80,54 +83,56 @@ public final class WidgetsPredicationUpdateTaskTest { @Before public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); MockitoAnnotations.initMocks(this); doAnswer(invocation -> { ComponentWithLabel componentWithLabel = invocation.getArgument(0); return componentWithLabel.getComponent().getShortClassName(); }).when(mIconCache).getTitleNoCache(any()); - mContext = RuntimeEnvironment.application; - mModelHelper = new LauncherModelHelper(); - mUserHandle = Process.myUserHandle(); + mUserHandle = myUserHandle(); + mApp1Provider1 = createAppWidgetProviderInfo( + ComponentName.createRelative("app1", "provider1")); + mApp1Provider2 = createAppWidgetProviderInfo( + ComponentName.createRelative("app1", "provider2")); + mApp2Provider1 = createAppWidgetProviderInfo( + ComponentName.createRelative("app2", "provider1")); + mApp4Provider1 = createAppWidgetProviderInfo( + ComponentName.createRelative("app4", "provider1")); + mApp4Provider2 = createAppWidgetProviderInfo( + ComponentName.createRelative("app4", ".provider2")); + mApp5Provider1 = createAppWidgetProviderInfo( + ComponentName.createRelative("app5", "provider1")); + allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1, + mApp4Provider1, mApp4Provider2, mApp5Provider1); + + AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class); + doReturn(allWidgets).when(manager).getInstalledProviders(); + doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle())); + doAnswer(i -> { + String pkg = i.getArgument(0); + Log.e("Hello", "Getting v " + pkg); + return TextUtils.isEmpty(pkg) ? allWidgets : allWidgets.stream() + .filter(a -> pkg.equals(a.provider.getPackageName())) + .collect(Collectors.toList()); + }).when(manager).getInstalledProvidersForPackage(any(), eq(myUserHandle())); + // 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace. - mModelHelper.initializeData("/widgets_predication_update_task_data.txt"); - - ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager()); - mApp1Provider1.provider = ComponentName.createRelative("app1", "provider1"); - ReflectionHelpers.setField(mApp1Provider1, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp1Provider1.provider)); - mApp1Provider2.provider = ComponentName.createRelative("app1", "provider2"); - ReflectionHelpers.setField(mApp1Provider2, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp1Provider2.provider)); - mApp2Provider1.provider = ComponentName.createRelative("app2", "provider1"); - ReflectionHelpers.setField(mApp2Provider1, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp2Provider1.provider)); - mApp4Provider1.provider = ComponentName.createRelative("app4", "provider1"); - ReflectionHelpers.setField(mApp4Provider1, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp4Provider1.provider)); - mApp4Provider2.provider = ComponentName.createRelative("app4", ".provider2"); - ReflectionHelpers.setField(mApp4Provider2, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp4Provider2.provider)); - mApp5Provider1.provider = ComponentName.createRelative("app5", "provider1"); - ReflectionHelpers.setField(mApp5Provider1, "providerInfo", - packageManager.addReceiverIfNotPresent(mApp5Provider1.provider)); - - ShadowAppWidgetManager shadowAppWidgetManager = - shadowOf(mContext.getSystemService(AppWidgetManager.class)); - shadowAppWidgetManager.addInstalledProvider(mApp1Provider1); - shadowAppWidgetManager.addInstalledProvider(mApp1Provider2); - shadowAppWidgetManager.addInstalledProvider(mApp2Provider1); - shadowAppWidgetManager.addInstalledProvider(mApp4Provider1); - shadowAppWidgetManager.addInstalledProvider(mApp4Provider2); - shadowAppWidgetManager.addInstalledProvider(mApp5Provider1); - - mModelHelper.getModel().addCallbacks(mCallback); + mModelHelper.initializeData("widgets_predication_update_task_data"); + MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get(); MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update( - LauncherAppState.getInstance(mContext), /* packageUser= */ null)); - waitUntilIdle(); + LauncherAppState.getInstance(mModelHelper.sandboxContext), + /* packageUser= */ null)); + + MODEL_EXECUTOR.submit(() -> { }).get(); + MAIN_EXECUTOR.submit(() -> { }).get(); } + @After + public void tearDown() { + mModelHelper.destroy(); + } @Test public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() @@ -165,9 +170,9 @@ public final class WidgetsPredicationUpdateTaskTest { @Test public void widgetsRecommendationRan_localFilterDisabled_shouldReturnWidgetsInPredicationOrder() throws Exception { - ShadowDeviceFlag shadowDeviceFlag = Shadow.extract( - FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER); - shadowDeviceFlag.setValue(false); + if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) { + return; + } // WHEN newPredicationTask is executed with 5 predicated widgets. AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1", @@ -203,13 +208,8 @@ public final class WidgetsPredicationUpdateTaskTest { assertThat(actual.getUser()).isEqualTo(expected.getProfile()); } - private void waitUntilIdle() { - shadowOf(MODEL_EXECUTOR.getLooper()).idle(); - shadowOf(MAIN_EXECUTOR.getLooper()).idle(); - } - private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) { - return new WidgetsPredictionUpdateTask( + return new WidgetsPredictionUpdateTask( new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"), appTargets); } diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java deleted file mode 100644 index e3694aeddc..0000000000 --- a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2020 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.secondarydisplay; - -import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; -import static com.android.launcher3.util.LauncherUIHelper.doLayout; -import static com.android.launcher3.util.Preconditions.assertNotNull; - -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.os.UserManager; -import android.provider.Settings; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.allapps.AllAppsPagedView; -import com.android.launcher3.allapps.AllAppsRecyclerView; -import com.android.launcher3.util.LauncherLayoutBuilder; -import com.android.launcher3.util.LauncherModelHelper; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowUserManager; - -/** - * Tests for {@link SecondaryDisplayLauncher} with work profile - */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) -public class SDWorkModeTest { - - private static final int SYSTEM_USER = 0; - private static final int FLAG_SYSTEM = 0x00000800; - private static final int WORK_PROFILE_ID = 10; - private static final int FLAG_PROFILE = 0x00001000; - - private Context mTargetContext; - private InvariantDeviceProfile mIdp; - private LauncherModelHelper mModelHelper; - - @Before - public void setup() throws Exception { - mModelHelper = new LauncherModelHelper(); - mTargetContext = RuntimeEnvironment.application; - mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); - Settings.Global.putFloat(mTargetContext.getContentResolver(), - Settings.Global.WINDOW_ANIMATION_SCALE, 0); - - mModelHelper.installApp(TEST_PACKAGE); - } - - @Test - public void testAllAppsList_noWorkProfile() throws Exception { - SecondaryDisplayLauncher launcher = loadLauncher(); - launcher.showAppDrawer(true); - doLayout(launcher); - - verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView()); - } - - @Test - public void testAllAppsList_workProfile() throws Exception { - ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class)); - sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM); - sum.addProfile(SYSTEM_USER, WORK_PROFILE_ID, "work", FLAG_PROFILE); - - SecondaryDisplayLauncher launcher = loadLauncher(); - launcher.showAppDrawer(true); - doLayout(launcher); - - AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView(); - verifyRecyclerViewCount(rv1); - - assertNotNull(launcher.getAppsView().getWorkModeSwitch()); - assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView); - - AllAppsPagedView pagedView = - (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer(); - pagedView.snapToPageImmediately(1); - doLayout(launcher); - - AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView(); - verifyRecyclerViewCount(rv2); - assertNotSame(rv1, rv2); - } - - private SecondaryDisplayLauncher loadLauncher() throws Exception { - // Install 100 apps - for (int i = 0; i < 100; i++) { - mModelHelper.installApp(TEST_PACKAGE + i); - } - mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync(); - SecondaryDisplayLauncher launcher = - Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get(); - doLayout(launcher); - return launcher; - } - - private void verifyRecyclerViewCount(AllAppsRecyclerView rv) { - int childCount = rv.getChildCount(); - assertTrue(childCount > 0); - assertTrue(childCount < 100); - } -} diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java deleted file mode 100644 index 17d0ac1a9f..0000000000 --- a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.testing; - -import com.android.launcher3.BaseActivity; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.BaseDragLayer; - -/** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */ -public class TestActivity extends BaseActivity implements ActivityContext { - - private DeviceProfile mDeviceProfile; - - @Override - public BaseDragLayer getDragLayer() { - return new BaseDragLayer(this, /* attrs= */ null, /* alphaChannelCount= */ 1) { - @Override - public void recreateControllers() { - // Do nothing. - } - }; - } - - @Override - public DeviceProfile getDeviceProfile() { - return mDeviceProfile; - } - - public void setDeviceProfile(DeviceProfile deviceProfile) { - mDeviceProfile = deviceProfile; - } -} diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java deleted file mode 100644 index caad40e260..0000000000 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2020 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 static android.view.View.MeasureSpec.EXACTLY; -import static android.view.View.MeasureSpec.makeMeasureSpec; - -import static com.android.launcher3.Utilities.createHomeIntent; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.graphics.Point; -import android.view.View; -import android.view.WindowManager; - -import com.android.launcher3.Launcher; - -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.shadows.ShadowLooper; -import org.robolectric.util.ReflectionHelpers; - -import java.util.List; - -/** - * Utility class to help manage Launcher UI and related objects for test. - */ -public class LauncherUIHelper { - - /** - * Returns the class name for the Launcher activity as defined in the manifest - */ - public static String getLauncherClassName() { - Context context = RuntimeEnvironment.application; - Intent homeIntent = createHomeIntent().setPackage(context.getPackageName()); - - List<ResolveInfo> launchers = context.getPackageManager() - .queryIntentActivities(homeIntent, 0); - if (launchers.size() != 1) { - return null; - } - return launchers.get(0).activityInfo.name; - } - - /** - * Returns an activity controller for Launcher activity defined in the manifest - */ - public static <T extends Launcher> ActivityController<T> buildLauncher() { - try { - Class<T> tClass = (Class<T>) Class.forName(getLauncherClassName()); - return Robolectric.buildActivity(tClass); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Creates and binds a Launcher activity defined in the manifest. - * Note that the model must be bound before calling this - */ - public static <T extends Launcher> T buildAndBindLauncher() { - ActivityController<T> controller = buildLauncher(); - - T launcher = controller.setup().get(); - doLayout(launcher); - ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor"); - if (executor != null) { - executor.markCompleted(); - } - return launcher; - } - - /** - * Performs a measure and layout pass for the given activity - */ - public static void doLayout(Activity activity) { - Point size = new Point(); - RuntimeEnvironment.application.getSystemService(WindowManager.class) - .getDefaultDisplay().getSize(size); - View view = activity.getWindow().getDecorView(); - view.measure(makeMeasureSpec(size.x, EXACTLY), makeMeasureSpec(size.y, EXACTLY)); - view.layout(0, 0, size.x, size.y); - ShadowLooper.idleMainLooper(); - } -} diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 702b73afba..50808244ad 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -50,7 +50,7 @@ import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.Themes; import com.android.launcher3.widget.custom.CustomWidgetManager; -public class LauncherAppState { +public class LauncherAppState implements SafeCloseable { public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher"; private static final String KEY_ICON_STATE = "pref_icon_shape_path"; @@ -158,7 +158,8 @@ public class LauncherAppState { /** * Call from Application.onTerminate(), which is not guaranteed to ever be called. */ - public void onTerminate() { + @Override + public void close() { mModel.destroy(); mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel); CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 7b6a5bfeb8..9ebec0a2ea 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -96,9 +96,10 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi // our monitoring of the package manager provides all updates and we never // need to do a requery. This is only ever touched from the loader thread. private boolean mModelLoaded; + private boolean mModelDestroyed = false; public boolean isModelLoaded() { synchronized (mLock) { - return mModelLoaded && mLoaderTask == null; + return mModelLoaded && mLoaderTask == null && !mModelDestroyed; } } @@ -245,6 +246,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * Called when the model is destroyed */ public void destroy() { + mModelDestroyed = true; MODEL_EXECUTOR.execute(mModelDelegate::destroy); } @@ -557,6 +559,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } public void enqueueModelUpdateTask(ModelUpdateTask task) { + if (mModelDestroyed) { + return; + } task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); MODEL_EXECUTOR.execute(task); } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 94c578e0ae..43ac8f98d1 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -87,7 +87,7 @@ import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; -import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; @@ -98,13 +98,10 @@ import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; /** @@ -123,22 +120,16 @@ public class LauncherPreviewRenderer extends ContextWrapper * Context used just for preview. It also provides a few objects (e.g. UserCache) just for * preview purposes. */ - public static class PreviewContext extends ContextWrapper { - - private final Set<MainThreadInitializedObject> mAllowedObjects = new HashSet<>( - Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE, - LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, - CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE)); + public static class PreviewContext extends SandboxContext { private final InvariantDeviceProfile mIdp; - private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>(); private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool = new ConcurrentLinkedQueue<>(); - private boolean mDestroyed = false; - public PreviewContext(Context base, InvariantDeviceProfile idp) { - super(base); + super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE, + LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, + CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE); mIdp = idp; mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); mObjectMap.put(LauncherAppState.INSTANCE, @@ -146,37 +137,6 @@ public class LauncherPreviewRenderer extends ContextWrapper } - @Override - public Context getApplicationContext() { - return this; - } - - public void onDestroy() { - CustomWidgetManager.INSTANCE.get(this).onDestroy(); - LauncherAppState.INSTANCE.get(this).onTerminate(); - mDestroyed = true; - } - - /** - * Find a cached object from mObjectMap if we have already created one. If not, generate - * an object using the provider. - */ - public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject, - MainThreadInitializedObject.ObjectProvider<T> provider) { - if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) { - throw new RuntimeException("Context already destroyed"); - } - if (!mAllowedObjects.contains(mainThreadInitializedObject)) { - throw new IllegalStateException("Leaking unknown objects"); - } - if (mObjectMap.containsKey(mainThreadInitializedObject)) { - return (T) mObjectMap.get(mainThreadInitializedObject); - } - T t = provider.get(this); - mObjectMap.put(mainThreadInitializedObject, t); - return t; - } - public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) { LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll(); if (launcherIconsForPreview != null) { diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index cd13cd0ec5..1a468aeb88 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -134,6 +134,9 @@ public class IconCache extends BaseIconCache { * Closes the cache DB. This will clear any in-memory cache. */ public void close() { + // This will clear all pending updates + getUpdateHandler(); + mIconDb.close(); } diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index e2c0a32bb0..94f29dbbf7 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -55,7 +55,7 @@ import java.util.Set; * Utility class to cache properties of default display to avoid a system RPC on every call. */ @SuppressLint("NewApi") -public class DisplayController implements DisplayListener, ComponentCallbacks { +public class DisplayController implements DisplayListener, ComponentCallbacks, SafeCloseable { private static final String TAG = "DisplayController"; @@ -79,6 +79,7 @@ public class DisplayController implements DisplayListener, ComponentCallbacks { private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>(); private Info mInfo; + private boolean mDestroyed = false; private DisplayController(Context context) { mContext = context; @@ -111,6 +112,17 @@ public class DisplayController implements DisplayListener, ComponentCallbacks { } @Override + public void close() { + mDestroyed = true; + if (mWindowContext != null) { + mWindowContext.unregisterComponentCallbacks(this); + } else { + // TODO: unregister broadcast receiver + } + mDM.unregisterDisplayListener(this); + } + + @Override public final void onDisplayAdded(int displayId) { } @Override @@ -157,6 +169,9 @@ public class DisplayController implements DisplayListener, ComponentCallbacks { * Only used for pre-S */ private void onConfigChanged(Intent intent) { + if (mDestroyed) { + return; + } Configuration config = mContext.getResources().getConfiguration(); if (mInfo.fontScale != config.fontScale || mInfo.densityDpi != config.densityDpi) { Log.d(TAG, "Configuration changed, notifying listeners"); diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index f6003dd7bf..badcd35c23 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -18,13 +18,21 @@ package com.android.launcher3.util; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.content.Context; +import android.content.ContextWrapper; import android.os.Looper; +import android.util.Log; +import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; -import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; import com.android.launcher3.util.ResourceBasedOverride.Overrides; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; /** @@ -40,8 +48,8 @@ public class MainThreadInitializedObject<T> { } public T get(Context context) { - if (context instanceof PreviewContext) { - return ((PreviewContext) context).getObject(this, mProvider); + if (context instanceof SandboxContext) { + return ((SandboxContext) context).getObject(this, mProvider); } if (mValue == null) { @@ -80,4 +88,80 @@ public class MainThreadInitializedObject<T> { T get(Context context); } + + /** + * Abstract Context which allows custom implementations for + * {@link MainThreadInitializedObject} providers + */ + public static abstract class SandboxContext extends ContextWrapper { + + private static final String TAG = "SandboxContext"; + + protected final Set<MainThreadInitializedObject> mAllowedObjects; + protected final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>(); + protected final ArrayList<Object> mOrderedObjects = new ArrayList<>(); + + private final Object mDestroyLock = new Object(); + private boolean mDestroyed = false; + + public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) { + super(base); + mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects)); + } + + @Override + public Context getApplicationContext() { + return this; + } + + public void onDestroy() { + synchronized (mDestroyLock) { + // Destroy in reverse order + for (int i = mOrderedObjects.size() - 1; i >= 0; i--) { + Object o = mOrderedObjects.get(i); + if (o instanceof SafeCloseable) { + ((SafeCloseable) o).close(); + } + } + mDestroyed = true; + } + } + + /** + * Find a cached object from mObjectMap if we have already created one. If not, generate + * an object using the provider. + */ + private <T> T getObject(MainThreadInitializedObject<T> object, ObjectProvider<T> provider) { + synchronized (mDestroyLock) { + if (mDestroyed) { + Log.e(TAG, "Static object access with a destroyed context"); + } + if (!mAllowedObjects.contains(object)) { + throw new IllegalStateException( + "Leaking unknown objects " + object + " " + provider); + } + T t = (T) mObjectMap.get(object); + if (t != null) { + return t; + } + if (Looper.myLooper() == Looper.getMainLooper()) { + t = createObject(provider); + mObjectMap.put(object, t); + mOrderedObjects.add(t); + return t; + } + } + + try { + return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @UiThread + protected <T> T createObject(ObjectProvider<T> provider) { + return provider.get(this); + } + } } diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java index 10611c7211..0c5b7225d3 100644 --- a/src/com/android/launcher3/util/SettingsCache.java +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -47,7 +47,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * * Cache will also be updated if a key queried is missing (even if it has no listeners registered). */ -public class SettingsCache extends ContentObserver { +public class SettingsCache extends ContentObserver implements SafeCloseable { /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ public static final Uri NOTIFICATION_BADGING_URI = @@ -69,7 +69,6 @@ public class SettingsCache extends ContentObserver { private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>(); protected final ContentResolver mResolver; - /** * Singleton instance */ @@ -82,6 +81,11 @@ public class SettingsCache extends ContentObserver { } @Override + public void close() { + mResolver.unregisterContentObserver(this); + } + + @Override public void onChange(boolean selfChange, Uri uri) { // We use default of 1, but if we're getting an onChange call, can assume a non-default // value will exist diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index 0f40179f79..ac5368c8b4 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -28,7 +28,7 @@ import android.os.Message; import android.view.View; import android.view.inputmethod.InputMethodManager; -import com.android.launcher3.Launcher; +import com.android.launcher3.BaseActivity; import com.android.launcher3.views.ActivityContext; /** @@ -56,7 +56,7 @@ public class UiThreadHelper { STATS_LOGGER_KEY, Message.obtain( HANDLER.get(root.getContext()), - () -> Launcher.cast(activityContext) + () -> BaseActivity.fromContext(root.getContext()) .getStatsLogManager() .logger() .log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED) diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java index 329a44452b..2e2a968a44 100644 --- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java +++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java @@ -33,6 +33,7 @@ import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.systemui.plugins.CustomWidgetPlugin; @@ -46,7 +47,7 @@ import java.util.stream.Stream; /** * CustomWidgetManager handles custom widgets implemented as a plugin. */ -public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> { +public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable { public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE = new MainThreadInitializedObject<>(CustomWidgetManager::new); @@ -71,7 +72,8 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> { .addPluginListener(this, CustomWidgetPlugin.class, true); } - public void onDestroy() { + @Override + public void close() { PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); } diff --git a/robolectric_tests/resources/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt index 603dbe3f92..603dbe3f92 100644 --- a/robolectric_tests/resources/cache_data_updated_task_data.txt +++ b/tests/res/raw/cache_data_updated_task_data.txt diff --git a/robolectric_tests/resources/db_schema_v10.json b/tests/res/raw/db_schema_v10.json index a5e290ef7a..a5e290ef7a 100644 --- a/robolectric_tests/resources/db_schema_v10.json +++ b/tests/res/raw/db_schema_v10.json diff --git a/robolectric_tests/resources/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt index e82ea9d4d6..e82ea9d4d6 100644 --- a/robolectric_tests/resources/package_install_state_change_task_data.txt +++ b/tests/res/raw/package_install_state_change_task_data.txt diff --git a/robolectric_tests/resources/widgets_predication_update_task_data.txt b/tests/res/raw/widgets_predication_update_task_data.txt index 941d1954e2..941d1954e2 100644 --- a/robolectric_tests/resources/widgets_predication_update_task_data.txt +++ b/tests/res/raw/widgets_predication_update_task_data.txt diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java index 8aa6f374aa..16f024e421 100644 --- a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -13,6 +13,9 @@ import android.content.Intent; import android.graphics.Rect; import android.util.Pair; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -21,19 +24,17 @@ import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.LauncherModelHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; import java.util.ArrayList; import java.util.List; @@ -41,8 +42,8 @@ import java.util.List; /** * Tests for {@link AddWorkspaceItemsTask} */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class AddWorkspaceItemsTaskTest { private final ComponentName mComponent1 = new ComponentName("a", "b"); @@ -60,7 +61,7 @@ public class AddWorkspaceItemsTaskTest { @Before public void setup() { mModelHelper = new LauncherModelHelper(); - mTargetContext = RuntimeEnvironment.application; + mTargetContext = mModelHelper.sandboxContext; mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); mIdp.numColumns = mIdp.numRows = 5; mAppState = LauncherAppState.getInstance(mTargetContext); @@ -70,6 +71,11 @@ public class AddWorkspaceItemsTaskTest { mNewScreens = new IntArray(); } + @After + public void tearDown() { + mModelHelper.destroy(); + } + private AddWorkspaceItemsTask newTask(ItemInfo... items) { List<Pair<ItemInfo, Object>> list = new ArrayList<>(); for (ItemInfo item : items) { @@ -80,6 +86,8 @@ public class AddWorkspaceItemsTaskTest { @Test public void testFindSpaceForItem_prefers_second() throws Exception { + mIdp.isSplitDisplay = false; + // First screen has only one hole of size 1 int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); @@ -88,7 +96,7 @@ public class AddWorkspaceItemsTaskTest { int[] spaceFound = newTask().findSpaceForItem( mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); - assertEquals(2, spaceFound[0]); + assertEquals(1, spaceFound[0]); assertTrue(mScreenOccupancy.get(spaceFound[0]) .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); @@ -101,6 +109,24 @@ public class AddWorkspaceItemsTaskTest { } @Test + public void testFindSpaceForItem_prefers_third_on_split_display() throws Exception { + mIdp.isSplitDisplay = true; + // First screen has only one hole of size 1 + int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + // Second screen has 2 holes of sizes 3x2 and 2x3 + setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); + + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); + // For split display, it picks the next screen, even if there is enough space + // on previous screen + assertEquals(2, spaceFound[0]); + assertTrue(mScreenOccupancy.get(spaceFound[0]) + .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); + } + + @Test public void testFindSpaceForItem_adds_new_screen() throws Exception { // First screen has 2 holes of sizes 3x2 and 2x3 setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); @@ -127,7 +153,7 @@ public class AddWorkspaceItemsTaskTest { @Test public void testAddItem_some_items_added() throws Exception { Callbacks callbacks = mock(Callbacks.class); - mModelHelper.getModel().addCallbacks(callbacks); + Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get(); WorkspaceItemInfo info = new WorkspaceItemInfo(); info.intent = new Intent().setComponent(mComponent1); diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/tests/src/com/android/launcher3/model/BackupRestoreTest.java index a397db565d..41914de1ab 100644 --- a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java +++ b/tests/src/com/android/launcher3/model/BackupRestoreTest.java @@ -17,6 +17,7 @@ package com.android.launcher3.model; import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE; +import static android.os.Process.myUserHandle; import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; @@ -26,74 +27,110 @@ import static com.android.launcher3.provider.LauncherDbUtils.tableExists; import static com.android.launcher3.util.LauncherModelHelper.APP_ICON; import static com.android.launcher3.util.LauncherModelHelper.NO__ICON; import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT; +import static com.android.launcher3.util.ReflectionHelpers.getField; +import static com.android.launcher3.util.ReflectionHelpers.setField; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.robolectric.util.ReflectionHelpers.setField; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import android.app.backup.BackupManager; import android.content.pm.PackageInstaller; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; +import android.util.ArrayMap; +import android.util.LongSparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.provider.RestoreDbTask; -import com.android.launcher3.shadows.LShadowBackupManager; import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.SafeCloseable; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowUserManager; /** * Tests to verify backup and restore flow. */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(LooperMode.Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class BackupRestoreTest { - private static final long MY_OLD_PROFILE_ID = 1; - private static final long MY_PROFILE_ID = 0; - private static final long OLD_WORK_PROFILE_ID = 11; - private static final int WORK_PROFILE_ID = 10; + private static final int PER_USER_RANGE = 200000; + + + private long mCurrentMyProfileId; + private long mOldMyProfileId; + + private long mCurrentWorkProfileId; + private long mOldWorkProfileId; - private ShadowUserManager mUserManager; private BackupManager mBackupManager; private LauncherModelHelper mModelHelper; private SQLiteDatabase mDb; private InvariantDeviceProfile mIdp; + private UserHandle mWorkUserHandle; + + private SafeCloseable mUserChangeListener; + @Before public void setUp() { + mModelHelper = new LauncherModelHelper(); + + mCurrentMyProfileId = mModelHelper.defaultProfileId; + mOldMyProfileId = mCurrentMyProfileId + 1; + mCurrentWorkProfileId = mOldMyProfileId + 1; + mOldWorkProfileId = mCurrentWorkProfileId + 1; + + mWorkUserHandle = UserHandle.getUserHandleForUid(PER_USER_RANGE); + mUserChangeListener = UserCache.INSTANCE.get(mModelHelper.sandboxContext) + .addUserChangeListener(() -> { }); + setupUserManager(); setupBackupManager(); - mModelHelper = new LauncherModelHelper(); - RestoreDbTask.setPending(RuntimeEnvironment.application); + RestoreDbTask.setPending(mModelHelper.sandboxContext); mDb = mModelHelper.provider.getDb(); - mIdp = InvariantDeviceProfile.INSTANCE.get(RuntimeEnvironment.application); + mIdp = InvariantDeviceProfile.INSTANCE.get(mModelHelper.sandboxContext); + + } + + @After + public void tearDown() { + mUserChangeListener.close(); + mModelHelper.destroy(); } private void setupUserManager() { - final UserManager userManager = RuntimeEnvironment.application.getSystemService( - UserManager.class); - mUserManager = Shadow.extract(userManager); - // sign in to work profile - mUserManager.addUser(WORK_PROFILE_ID, "work", ShadowUserManager.FLAG_MANAGED_PROFILE); + UserCache cache = UserCache.INSTANCE.get(mModelHelper.sandboxContext); + synchronized (cache) { + LongSparseArray<UserHandle> users = getField(cache, "mUsers"); + users.clear(); + users.put(mCurrentMyProfileId, myUserHandle()); + users.put(mCurrentWorkProfileId, mWorkUserHandle); + + ArrayMap<UserHandle, Long> userMap = getField(cache, "mUserToSerialMap"); + userMap.clear(); + userMap.put(myUserHandle(), mCurrentMyProfileId); + userMap.put(mWorkUserHandle, mCurrentWorkProfileId); + } } private void setupBackupManager() { - mBackupManager = new BackupManager(RuntimeEnvironment.application); - final LShadowBackupManager bm = Shadow.extract(mBackupManager); - bm.addProfile(MY_OLD_PROFILE_ID, Process.myUserHandle()); - bm.addProfile(OLD_WORK_PROFILE_ID, UserHandle.of(WORK_PROFILE_ID)); + mBackupManager = spy(new BackupManager(mModelHelper.sandboxContext)); + doReturn(myUserHandle()).when(mBackupManager) + .getUserForAncestralSerialNumber(eq(mOldMyProfileId)); + doReturn(mWorkUserHandle).when(mBackupManager) + .getUserForAncestralSerialNumber(eq(mOldWorkProfileId)); } @Test @@ -118,18 +155,18 @@ public class BackupRestoreTest { { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON}, { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT}, { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON}, - }}, 1, MY_OLD_PROFILE_ID); + }}, 1, mOldMyProfileId); // setup grid for work profile on second screen mModelHelper.createGrid(new int[][][]{{ { NO__ICON, APP_ICON, SHORTCUT, SHORTCUT}, { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON}, { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT}, { APP_ICON, SHORTCUT, SHORTCUT, NO__ICON}, - }}, 2, OLD_WORK_PROFILE_ID); + }}, 2, mOldWorkProfileId); // simulates the creation of backup upon restore - new GridBackupTable(RuntimeEnvironment.application, mDb, mIdp.numDatabaseHotseatIcons, + new GridBackupTable(mModelHelper.sandboxContext, mDb, mIdp.numDatabaseHotseatIcons, mIdp.numColumns, mIdp.numRows).doBackup( - MY_OLD_PROFILE_ID, GridBackupTable.OPTION_REQUIRES_SANITIZATION); + mOldMyProfileId, GridBackupTable.OPTION_REQUIRES_SANITIZATION); // reset favorites table createTableUsingOldProfileId(); } @@ -141,28 +178,28 @@ public class BackupRestoreTest { private void verifyTableIsFilled(String tableName, boolean sanitized) { assertEquals(sanitized ? 12 : 13, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = " - + (sanitized ? MY_PROFILE_ID : MY_OLD_PROFILE_ID))); + + (sanitized ? mCurrentMyProfileId : mOldMyProfileId))); assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = " - + (sanitized ? WORK_PROFILE_ID : OLD_WORK_PROFILE_ID))); + + (sanitized ? mCurrentWorkProfileId : mOldWorkProfileId))); } private void createTableUsingOldProfileId() { // simulates the creation of favorites table on old device dropTable(mDb, TABLE_NAME); - addTableToDb(mDb, MY_OLD_PROFILE_ID, false); + addTableToDb(mDb, mOldMyProfileId, false); } private void createRestoreSession() throws Exception { final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); - final PackageInstaller installer = RuntimeEnvironment.application.getPackageManager() + final PackageInstaller installer = mModelHelper.sandboxContext.getPackageManager() .getPackageInstaller(); final int sessionId = installer.createSession(params); final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId); setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE); // TODO: (b/148410677) we should verify the following call instead // InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info); - RestoreDbTask.restoreIfPossible(RuntimeEnvironment.application, + RestoreDbTask.restoreIfPossible(mModelHelper.sandboxContext, mModelHelper.provider.getHelper(), mBackupManager); } diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java index 9ac3fe761e..dba0a4063f 100644 --- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java +++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java @@ -16,6 +16,8 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; import com.android.launcher3.LauncherAppState; import com.android.launcher3.icons.BitmapInfo; @@ -26,13 +28,10 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.LauncherModelHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; import java.util.Arrays; import java.util.HashSet; @@ -40,8 +39,8 @@ import java.util.HashSet; /** * Tests for {@link CacheDataUpdatedTask} */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class CacheDataUpdatedTaskTest { private static final String NEW_LABEL_PREFIX = "new-label-"; @@ -51,10 +50,10 @@ public class CacheDataUpdatedTaskTest { @Before public void setup() throws Exception { mModelHelper = new LauncherModelHelper(); - mModelHelper.initializeData("/cache_data_updated_task_data.txt"); + mModelHelper.initializeData("cache_data_updated_task_data"); // Add placeholder entries in the cache to simulate update - Context context = RuntimeEnvironment.application; + Context context = mModelHelper.sandboxContext; IconCache iconCache = LauncherAppState.getInstance(context).getIconCache(); CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() { @Override @@ -86,6 +85,11 @@ public class CacheDataUpdatedTaskTest { } } + @After + public void tearDown() { + mModelHelper.destroy(); + } + private CacheDataUpdatedTask newTask(int op, String... pkg) { return new CacheDataUpdatedTask(op, Process.myUserHandle(), new HashSet<>(Arrays.asList(pkg))); diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java index be03c7dd4b..d849c8fff9 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -15,12 +15,13 @@ */ package com.android.launcher3.model; +import static androidx.test.InstrumentationRegistry.getContext; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -32,6 +33,10 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherProvider.DatabaseHelper; import com.android.launcher3.LauncherSettings.Favorites; @@ -40,15 +45,14 @@ import com.android.launcher3.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import java.io.File; /** * Tests for {@link DbDowngradeHelper} */ -@RunWith(RobolectricTestRunner.class) +@SmallTest +@RunWith(AndroidJUnit4.class) public class DbDowngradeHelperTest { private static final String SCHEMA_FILE = "test_schema.json"; @@ -60,7 +64,7 @@ public class DbDowngradeHelperTest { @Before public void setup() { - mContext = RuntimeEnvironment.application; + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE); mDbFile = mContext.getDatabasePath(DB_FILE); } @@ -77,8 +81,10 @@ public class DbDowngradeHelperTest { public void testUpdateSchemaFile() throws Exception { // Setup mock resources Resources res = spy(mContext.getResources()); - doAnswer(i ->this.getClass().getResourceAsStream("/db_schema_v10.json")) - .when(res).openRawResource(eq(R.raw.downgrade_schema)); + Resources myRes = getContext().getResources(); + doAnswer(i -> myRes.openRawResource( + myRes.getIdentifier("db_schema_v10", "raw", getContext().getPackageName()))) + .when(res).openRawResource(R.raw.downgrade_schema); Context context = spy(mContext); when(context.getResources()).thenReturn(res); diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java index 655237d08c..004ed06b32 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -16,18 +16,18 @@ package com.android.launcher3.model; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; import static org.junit.Assert.assertEquals; -import static org.robolectric.Shadows.shadowOf; -import static org.robolectric.util.ReflectionHelpers.setField; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.LauncherSettings; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.model.data.FolderInfo; @@ -35,19 +35,16 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.LauncherModelHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; /** * Tests for layout parser for remote layout */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class DefaultLayoutProviderTest { private LauncherModelHelper mModelHelper; @@ -56,16 +53,18 @@ public class DefaultLayoutProviderTest { @Before public void setUp() { mModelHelper = new LauncherModelHelper(); - mTargetContext = RuntimeEnvironment.application; + mTargetContext = mModelHelper.sandboxContext; + } - shadowOf(mTargetContext.getPackageManager()) - .addActivityIfNotPresent(new ComponentName(TEST_PACKAGE, TEST_PACKAGE)); + @After + public void tearDown() { + mModelHelper.destroy(); } @Test public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) - .putApp(TEST_PACKAGE, TEST_PACKAGE)); + .putApp(TEST_PACKAGE, TEST_ACTIVITY)); // Verify one item in hotseat assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); @@ -77,9 +76,9 @@ public class DefaultLayoutProviderTest { @Test public void testCustomProfileLoaded_with_folder() throws Exception { writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) - .addApp(TEST_PACKAGE, TEST_PACKAGE) - .addApp(TEST_PACKAGE, TEST_PACKAGE) - .addApp(TEST_PACKAGE, TEST_PACKAGE) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) .build()); // Verify folder @@ -92,9 +91,9 @@ public class DefaultLayoutProviderTest { @Test public void testCustomProfileLoaded_with_folder_custom_title() throws Exception { writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder") - .addApp(TEST_PACKAGE, TEST_PACKAGE) - .addApp(TEST_PACKAGE, TEST_PACKAGE) - .addApp(TEST_PACKAGE, TEST_PACKAGE) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) + .addApp(TEST_PACKAGE, TEST_ACTIVITY) .build()); // Verify folder @@ -112,12 +111,10 @@ public class DefaultLayoutProviderTest { // Add a placeholder session info so that the widget exists SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); params.setAppPackageName(pendingAppPkg); + params.setAppIcon(BitmapInfo.LOW_RES_ICON); PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); - int sessionId = installer.createSession(params); - SessionInfo sessionInfo = installer.getSessionInfo(sessionId); - setField(sessionInfo, "installerPackageName", "com.test"); - setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON); + installer.createSession(params); writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0) .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2)); diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java index 87b08870c8..005389e5bb 100644 --- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java +++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java @@ -30,26 +30,31 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import android.os.Process; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherSettings; import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.LauncherModelHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; +import java.util.HashMap; import java.util.HashSet; /** Unit tests for {@link GridSizeMigrationTaskV2} */ -@RunWith(RobolectricTestRunner.class) +@SmallTest +@RunWith(AndroidJUnit4.class) public class GridSizeMigrationTaskV2Test { private LauncherModelHelper mModelHelper; @@ -73,7 +78,7 @@ public class GridSizeMigrationTaskV2Test { @Before public void setUp() { mModelHelper = new LauncherModelHelper(); - mContext = RuntimeEnvironment.application; + mContext = mModelHelper.sandboxContext; mDb = mModelHelper.provider.getDb(); mValidPackages = new HashSet<>(); @@ -98,8 +103,13 @@ public class GridSizeMigrationTaskV2Test { LauncherSettings.Favorites.TMP_TABLE); } + @After + public void tearDown() { + mModelHelper.destroy(); + } + @Test - public void testMigration() { + public void testMigration() throws Exception { int[] srcHotseatItems = { mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI), mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI), @@ -134,17 +144,17 @@ public class GridSizeMigrationTaskV2Test { // Check hotseat items Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT}, - "container=" + CONTAINER_HOTSEAT, null, null, null); + "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null); assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons); int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN); int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT); c.moveToNext(); - assertEquals(c.getInt(screenIndex), 1); - assertTrue(c.getString(intentIndex).contains(testPackage2)); - c.moveToNext(); assertEquals(c.getInt(screenIndex), 0); assertTrue(c.getString(intentIndex).contains(testPackage1)); c.moveToNext(); + assertEquals(c.getInt(screenIndex), 1); + assertTrue(c.getString(intentIndex).contains(testPackage2)); + c.moveToNext(); assertEquals(c.getInt(screenIndex), 2); assertTrue(c.getString(intentIndex).contains(testPackage3)); c.moveToNext(); @@ -157,35 +167,24 @@ public class GridSizeMigrationTaskV2Test { new String[]{LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, LauncherSettings.Favorites.INTENT}, "container=" + CONTAINER_DESKTOP, null, null, null); - assertEquals(c.getCount(), 6); intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT); int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX); int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage7)); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage6)); - assertEquals(c.getInt(cellXIndex), 0); - assertEquals(c.getInt(cellYIndex), 3); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage10)); - assertEquals(c.getInt(cellXIndex), 1); - assertEquals(c.getInt(cellYIndex), 3); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage5)); - assertEquals(c.getInt(cellXIndex), 2); - assertEquals(c.getInt(cellYIndex), 3); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage9)); - assertEquals(c.getInt(cellXIndex), 3); - assertEquals(c.getInt(cellYIndex), 3); - c.moveToNext(); - assertTrue(c.getString(intentIndex).contains(testPackage8)); - assertEquals(c.getInt(cellXIndex), 0); - assertEquals(c.getInt(cellYIndex), 2); - + HashMap<String, Point> locMap = new HashMap<>(); + while (c.moveToNext()) { + locMap.put( + Intent.parseUri(c.getString(intentIndex), 0).getPackage(), + new Point(c.getInt(cellXIndex), c.getInt(cellYIndex))); + } c.close(); + + assertEquals(locMap.size(), 6); + assertEquals(new Point(0, 2), locMap.get(testPackage8)); + assertEquals(new Point(0, 3), locMap.get(testPackage6)); + assertEquals(new Point(1, 3), locMap.get(testPackage10)); + assertEquals(new Point(2, 3), locMap.get(testPackage5)); + assertEquals(new Point(3, 3), locMap.get(testPackage9)); } @Test @@ -212,7 +211,7 @@ public class GridSizeMigrationTaskV2Test { // Check hotseat items Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT}, - "container=" + CONTAINER_HOTSEAT, null, null, null); + "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null); assertEquals(c.getCount(), numSrcDatabaseHotseatIcons); int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN); int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT); @@ -257,7 +256,7 @@ public class GridSizeMigrationTaskV2Test { // Check hotseat items Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT}, - "container=" + CONTAINER_HOTSEAT, null, null, null); + "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null); assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons); int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN); int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT); diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 800311afb6..6444ef6927 100644 --- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static androidx.test.InstrumentationRegistry.getContext; + import static com.android.launcher3.LauncherSettings.Favorites.CELLX; import static com.android.launcher3.LauncherSettings.Favorites.CELLY; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; @@ -33,7 +35,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.RESTORED; import static com.android.launcher3.LauncherSettings.Favorites.SCREEN; import static com.android.launcher3.LauncherSettings.Favorites.TITLE; import static com.android.launcher3.LauncherSettings.Favorites._ID; -import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; +import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -41,37 +43,37 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.robolectric.Shadows.shadowOf; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.database.MatrixCursor; import android.os.Process; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.util.PackageManagerHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; /** * Tests for {@link LoaderCursor} */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class LoaderCursorTest { + private LauncherModelHelper mModelHelper; private LauncherAppState mApp; private MatrixCursor mCursor; @@ -82,7 +84,8 @@ public class LoaderCursorTest { @Before public void setup() { - mContext = RuntimeEnvironment.application; + mModelHelper = new LauncherModelHelper(); + mContext = mModelHelper.sandboxContext; mIDP = InvariantDeviceProfile.INSTANCE.get(mContext); mApp = LauncherAppState.getInstance(mContext); @@ -97,6 +100,11 @@ public class LoaderCursorTest { ums.allUsers.put(0, Process.myUserHandle()); } + @After + public void tearDown() { + mModelHelper.destroy(); + } + private void initCursor(int itemType, String title) { mCursor.newRow() .add(_ID, 1) @@ -117,9 +125,7 @@ public class LoaderCursorTest { @Test public void getAppShortcutInfo_dontAllowMissing_validComponent() throws Exception { - ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_PACKAGE); - shadowOf(mContext.getPackageManager()).addActivityIfNotPresent(cn); - + ComponentName cn = new ComponentName(getContext(), TEST_ACTIVITY); initCursor(ITEM_TYPE_APPLICATION, ""); assertTrue(mLoaderCursor.moveToNext()); diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java index 43193553fe..42c9f11a52 100644 --- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java +++ b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java @@ -15,69 +15,60 @@ */ package com.android.launcher3.model; -import static com.android.launcher3.util.Executors.createAndStartNewLooper; import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; -import static org.robolectric.Shadows.shadowOf; import android.os.Process; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.shadows.ShadowLooperExecutor; import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.LauncherModelHelper; -import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.RunnableList; +import com.android.launcher3.util.TestUtil; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** * Tests to verify multiple callbacks in Loader */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class ModelMultiCallbacksTest { private LauncherModelHelper mModelHelper; - private ShadowPackageManager mSpm; - private LooperExecutor mTempMainExecutor; - @Before - public void setUp() throws Exception { + public void setUp() { mModelHelper = new LauncherModelHelper(); - mModelHelper.installApp(TEST_PACKAGE); - - mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager()); + } - // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread, - // so that we can wait appropriately for the loader to complete. - mTempMainExecutor = new LooperExecutor(createAndStartNewLooper("tempMain")); - ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR); - sle.setHandler(mTempMainExecutor.getHandler()); + @After + public void tearDown() throws Exception { + mModelHelper.destroy(); + TestUtil.uninstallDummyApp(); } @Test @@ -85,7 +76,7 @@ public class ModelMultiCallbacksTest { setupWorkspacePages(3); MyCallbacks cb1 = spy(MyCallbacks.class); - mModelHelper.getModel().addCallbacksAndLoad(cb1); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1)); waitForLoaderAndTempMainThread(); cb1.verifySynchronouslyBound(3); @@ -94,10 +85,10 @@ public class ModelMultiCallbacksTest { cb1.reset(); MyCallbacks cb2 = spy(MyCallbacks.class); cb2.mPageToBindSync = IntSet.wrap(2); - mModelHelper.getModel().addCallbacksAndLoad(cb2); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2)); waitForLoaderAndTempMainThread(); - cb1.verifySynchronouslyBound(3); + assertFalse(cb1.bindStarted); cb2.verifySynchronouslyBound(3); // Remove callbacks @@ -105,7 +96,7 @@ public class ModelMultiCallbacksTest { cb2.reset(); // No effect on callbacks when removing an callback - mModelHelper.getModel().removeCallbacks(cb2); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2)); waitForLoaderAndTempMainThread(); assertNull(cb1.mPendingTasks); assertNull(cb2.mPendingTasks); @@ -119,52 +110,48 @@ public class ModelMultiCallbacksTest { @Test public void testTwoCallbacks_receiveUpdates() throws Exception { + TestUtil.uninstallDummyApp(); + setupWorkspacePages(1); MyCallbacks cb1 = spy(MyCallbacks.class); MyCallbacks cb2 = spy(MyCallbacks.class); - mModelHelper.getModel().addCallbacksAndLoad(cb1); - mModelHelper.getModel().addCallbacksAndLoad(cb2); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1)); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2)); waitForLoaderAndTempMainThread(); - cb1.verifyApps(TEST_PACKAGE); - cb2.verifyApps(TEST_PACKAGE); + assertTrue(cb1.allApps().contains(TEST_PACKAGE)); + assertTrue(cb2.allApps().contains(TEST_PACKAGE)); // Install package 1 - String pkg1 = "com.test.pkg1"; - mModelHelper.installApp(pkg1); - mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle()); - waitForLoaderAndTempMainThread(); - cb1.verifyApps(TEST_PACKAGE, pkg1); - cb2.verifyApps(TEST_PACKAGE, pkg1); - - // Install package 2 - String pkg2 = "com.test.pkg2"; - mModelHelper.installApp(pkg2); - mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle()); + TestUtil.installDummyApp(); + mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); waitForLoaderAndTempMainThread(); - cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2); - cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2); + assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); + assertTrue(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); // Uninstall package 2 - mSpm.removePackage(pkg1); - mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle()); + TestUtil.uninstallDummyApp(); + mModelHelper.getModel().onPackageRemoved(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); waitForLoaderAndTempMainThread(); - cb1.verifyApps(TEST_PACKAGE, pkg2); - cb2.verifyApps(TEST_PACKAGE, pkg2); + assertFalse(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); + assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); // Unregister a callback and verify updates no longer received - mModelHelper.getModel().removeCallbacks(cb2); - mSpm.removePackage(pkg2); - mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle()); + Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2)); + TestUtil.installDummyApp(); + mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); waitForLoaderAndTempMainThread(); - cb1.verifyApps(TEST_PACKAGE); - cb2.verifyApps(TEST_PACKAGE, pkg2); + + // cb2 didn't get the update + assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); + assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); } private void waitForLoaderAndTempMainThread() throws Exception { + Executors.MAIN_EXECUTOR.submit(() -> { }).get(); Executors.MODEL_EXECUTOR.submit(() -> { }).get(); - mTempMainExecutor.submit(() -> { }).get(); + Executors.MAIN_EXECUTOR.submit(() -> { }).get(); } private void setupWorkspacePages(int pageCount) throws Exception { @@ -183,10 +170,16 @@ public class ModelMultiCallbacksTest { IntSet mPageBoundSync = new IntSet(); RunnableList mPendingTasks; AppInfo[] mAppInfos; + boolean bindStarted; MyCallbacks() { } @Override + public void startBinding() { + bindStarted = true; + } + + @Override public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) { mPageBoundSync = boundPages; mPendingTasks = pendingTasks; @@ -212,10 +205,12 @@ public class ModelMultiCallbacksTest { mPageBoundSync = new IntSet(); mPendingTasks = null; mAppInfos = null; + bindStarted = false; } public void verifySynchronouslyBound(int totalItems) { // Verify that the requested page is bound synchronously + assertTrue(bindStarted); assertEquals(mPageToBindSync, mPageBoundSync); assertEquals(mItems.size(), 1); assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync); @@ -226,12 +221,14 @@ public class ModelMultiCallbacksTest { assertEquals(mItems.size(), totalItems); } - public void verifyApps(String... apps) { - assertEquals(apps.length, mAppInfos.length); - assertEquals(Arrays.stream(mAppInfos) + public Set<String> allApps() { + return Arrays.stream(mAppInfos) .map(ai -> ai.getTargetComponent().getPackageName()) - .collect(Collectors.toSet()), - new HashSet<>(Arrays.asList(apps))); + .collect(Collectors.toSet()); + } + + public void verifyApps(String... apps) { + assertTrue(allApps().containsAll(Arrays.asList(apps))); } } } diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index 412ace03ff..519191e251 100644 --- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -2,18 +2,19 @@ package com.android.launcher3.model; import static org.junit.Assert.assertEquals; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.PackageInstallInfo; import com.android.launcher3.util.LauncherModelHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.LooperMode.Mode; import java.util.Arrays; import java.util.HashSet; @@ -21,8 +22,8 @@ import java.util.HashSet; /** * Tests for {@link PackageInstallStateChangedTask} */ -@RunWith(RobolectricTestRunner.class) -@LooperMode(Mode.PAUSED) +@SmallTest +@RunWith(AndroidJUnit4.class) public class PackageInstallStateChangedTaskTest { private LauncherModelHelper mModelHelper; @@ -30,7 +31,12 @@ public class PackageInstallStateChangedTaskTest { @Before public void setup() throws Exception { mModelHelper = new LauncherModelHelper(); - mModelHelper.initializeData("/package_install_state_change_task_data.txt"); + mModelHelper.initializeData("package_install_state_change_task_data"); + } + + @After + public void tearDown() { + mModelHelper.destroy(); } private PackageInstallStateChangedTask newTask(String pkg, int progress) { @@ -66,7 +72,7 @@ public class PackageInstallStateChangedTaskTest { HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated)); for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { if (info instanceof WorkspaceItemInfo) { - assertEquals(updates.contains(info.id) ? progress: 0, + assertEquals(updates.contains(info.id) ? progress: 100, ((WorkspaceItemInfo) info).getProgressLevel()); } else { assertEquals(updates.contains(info.id) ? progress: -1, diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 4184d33a68..48305eebdf 100644 --- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -21,18 +21,21 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.launcher3.LauncherProvider.DatabaseHelper; import com.android.launcher3.LauncherSettings.Favorites; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; /** * Tests for {@link RestoreDbTask} */ -@RunWith(RobolectricTestRunner.class) +@SmallTest +@RunWith(AndroidJUnit4.class) public class RestoreDbTaskTest { @Test @@ -95,7 +98,7 @@ public class RestoreDbTaskTest { private final long mProfileId; MyDatabaseHelper(long profileId) { - super(RuntimeEnvironment.application, null, false); + super(InstrumentationRegistry.getInstrumentation().getTargetContext(), null, false); mProfileId = profileId; } diff --git a/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java new file mode 100644 index 0000000000..fd86cf1144 --- /dev/null +++ b/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.secondarydisplay; + +import static androidx.test.core.app.ActivityScenario.launch; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.MediumTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SecondaryDisplayLauncher} + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class SDLauncherTest { + + @Before + public void setUp() { + Intents.init(); + } + + @After + public void tearDown() { + Intents.release(); + } + + @Test + public void testAllAppsListOpens() { + ActivityScenario<SecondaryDisplayLauncher> launcher = + launch(SecondaryDisplayLauncher.class); + launcher.onActivity(l -> l.showAppDrawer(true)); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java index 4e21dce021..4e21dce021 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java +++ b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java index 846e2019a0..c9b63aeb11 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -15,26 +15,40 @@ */ package com.android.launcher3.util; -import static android.content.Intent.ACTION_CREATE_SHORTCUT; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.robolectric.Shadows.shadowOf; import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.os.Process; import android.provider.Settings; +import android.test.mock.MockContentResolver; +import android.util.ArrayMap; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.uiautomator.UiDevice; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -45,27 +59,30 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.shadows.ShadowLooperExecutor; +import com.android.launcher3.testing.TestInformationProvider; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider; +import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; +import com.android.launcher3.widget.custom.CustomWidgetManager; import org.mockito.ArgumentCaptor; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowContentResolver; -import org.robolectric.shadows.ShadowPackageManager; -import org.robolectric.util.ReflectionHelpers; import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Function; @@ -81,7 +98,9 @@ public class LauncherModelHelper { public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; public static final int NO__ICON = -1; - public static final String TEST_PACKAGE = "com.android.launcher3.validpackage"; + + public static final String TEST_PACKAGE = testContext().getPackageName(); + public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2"; // Authority for providing a test default-workspace-layout data. private static final String TEST_PROVIDER_AUTHORITY = @@ -90,21 +109,42 @@ public class LauncherModelHelper { private static final int DEFAULT_GRID_SIZE = 4; private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>(); + private final MockContentResolver mMockResolver = new MockContentResolver(); public final TestLauncherProvider provider; - private final long mDefaultProfileId; + public final SanboxModelContext sandboxContext; + + public final long defaultProfileId; private BgDataModel mDataModel; private AllAppsList mAllAppsList; public LauncherModelHelper() { - provider = Robolectric.setupContentProvider(TestLauncherProvider.class); - mDefaultProfileId = UserCache.INSTANCE.get(RuntimeEnvironment.application) + Context context = getApplicationContext(); + // System settings cache content provider. Ensure that they are statically initialized + Settings.Secure.getString(context.getContentResolver(), "test"); + Settings.System.getString(context.getContentResolver(), "test"); + Settings.Global.getString(context.getContentResolver(), "test"); + + provider = new TestLauncherProvider(); + sandboxContext = new SanboxModelContext(); + defaultProfileId = UserCache.INSTANCE.get(sandboxContext) .getSerialNumberForUser(Process.myUserHandle()); - ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider); + setupProvider(LauncherProvider.AUTHORITY, provider); + } + + protected void setupProvider(String authority, ContentProvider provider) { + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = authority; + providerInfo.applicationInfo = sandboxContext.getApplicationInfo(); + provider.attachInfo(sandboxContext, providerInfo); + mMockResolver.addProvider(providerInfo.authority, provider); + doReturn(providerInfo) + .when(sandboxContext.mPm) + .resolveContentProvider(eq(authority), anyInt()); } public LauncherModel getModel() { - return LauncherAppState.getInstance(RuntimeEnvironment.application).getModel(); + return LauncherAppState.getInstance(sandboxContext).getModel(); } public synchronized BgDataModel getBgDataModel() { @@ -121,6 +161,28 @@ public class LauncherModelHelper { return mAllAppsList; } + public void destroy() { + // When destroying the context, make sure that the model thread is blocked, so that no + // new jobs get posted while we are cleaning up + CountDownLatch l1 = new CountDownLatch(1); + CountDownLatch l2 = new CountDownLatch(1); + MODEL_EXECUTOR.execute(() -> { + l1.countDown(); + waitOrThrow(l2); + }); + waitOrThrow(l1); + sandboxContext.onDestroy(); + l2.countDown(); + } + + private void waitOrThrow(CountDownLatch latch) { + try { + latch.await(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * Synchronously executes the task and returns all the UI callbacks posted. */ @@ -161,13 +223,16 @@ public class LauncherModelHelper { * Initializes mock data for the test. */ public void initializeData(String resourceName) throws Exception { - Context targetContext = RuntimeEnvironment.application; BgDataModel bgDataModel = getBgDataModel(); AllAppsList allAppsList = getAllAppsList(); MODEL_EXECUTOR.submit(() -> { + // Copy apk from resources to a local file and install from there. + Resources resources = testContext().getResources(); + int resId = resources.getIdentifier( + resourceName, "raw", testContext().getPackageName()); try (BufferedReader reader = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(resourceName)))) { + resources.openRawResource(resId)))) { String line; HashMap<String, Class> classMap = new HashMap<>(); while ((line = reader.readLine()) != null) { @@ -181,7 +246,7 @@ public class LauncherModelHelper { classMap.put(commands[1], Class.forName(commands[2])); break; case "bgItem": - bgDataModel.addItem(targetContext, + bgDataModel.addItem(sandboxContext, (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false); break; @@ -236,7 +301,7 @@ public class LauncherModelHelper { } public int addItem(int type, int screen, int container, int x, int y) { - return addItem(type, screen, container, x, y, mDefaultProfileId, TEST_PACKAGE); + return addItem(type, screen, container, x, y, defaultProfileId, TEST_PACKAGE); } public int addItem(int type, int screen, int container, int x, int y, long profileId) { @@ -244,12 +309,12 @@ public class LauncherModelHelper { } public int addItem(int type, int screen, int container, int x, int y, String packageName) { - return addItem(type, screen, container, x, y, mDefaultProfileId, packageName); + return addItem(type, screen, container, x, y, defaultProfileId, packageName); } public int addItem(int type, int screen, int container, int x, int y, String packageName, int id, Uri contentUri) { - addItem(type, screen, container, x, y, mDefaultProfileId, packageName, id, contentUri); + addItem(type, screen, container, x, y, defaultProfileId, packageName, id, contentUri); return id; } @@ -260,8 +325,7 @@ public class LauncherModelHelper { */ public int addItem(int type, int screen, int container, int x, int y, long profileId, String packageName) { - Context context = RuntimeEnvironment.application; - int id = LauncherSettings.Settings.call(context.getContentResolver(), + int id = LauncherSettings.Settings.call(sandboxContext.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_ITEM_ID) .getInt(LauncherSettings.Settings.EXTRA_VALUE); addItem(type, screen, container, x, y, profileId, packageName, id, CONTENT_URI); @@ -270,8 +334,6 @@ public class LauncherModelHelper { public void addItem(int type, int screen, int container, int x, int y, long profileId, String packageName, int id, Uri contentUri) { - Context context = RuntimeEnvironment.application; - ContentValues values = new ContentValues(); values.put(LauncherSettings.Favorites._ID, id); values.put(LauncherSettings.Favorites.CONTAINER, container); @@ -295,7 +357,7 @@ public class LauncherModelHelper { } } - context.getContentResolver().insert(contentUri, values); + sandboxContext.getContentResolver().insert(contentUri, values); } public int[][][] createGrid(int[][][] typeArray) { @@ -303,12 +365,11 @@ public class LauncherModelHelper { } public int[][][] createGrid(int[][][] typeArray, int startScreen) { - final Context context = RuntimeEnvironment.application; - LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.call(sandboxContext.getContentResolver(), LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); - LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.call(sandboxContext.getContentResolver(), LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); - return createGrid(typeArray, startScreen, mDefaultProfileId); + return createGrid(typeArray, startScreen, defaultProfileId); } /** @@ -320,14 +381,13 @@ public class LauncherModelHelper { * @return the same grid representation where each entry is the corresponding item id. */ public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) { - Context context = RuntimeEnvironment.application; int[][][] ids = new int[typeArray.length][][]; for (int i = 0; i < typeArray.length; i++) { // Add screen to DB int screenId = startScreen + i; // Keep the screen id counter up to date - LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.call(sandboxContext.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); ids[i] = new int[typeArray[i].length][]; @@ -353,69 +413,45 @@ public class LauncherModelHelper { */ public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception { - Context context = RuntimeEnvironment.application; - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context); + InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext); idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE; idp.iconBitmapSize = DEFAULT_BITMAP_SIZE; - Settings.Secure.putString(context.getContentResolver(), - "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY); - - shadowOf(context.getPackageManager()) - .addProviderIfNotPresent(new ComponentName("com.test", "Mock")).authority = - TEST_PROVIDER_AUTHORITY; + UiDevice.getInstance(getInstrumentation()).executeShellCommand( + "settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY); + ContentProvider cp = new TestInformationProvider() { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - builder.build(new OutputStreamWriter(bos)); - Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context); - shadowOf(context.getContentResolver()).registerInputStream(layoutUri, - new ByteArrayInputStream(bos.toByteArray())); + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) + throws FileNotFoundException { + try { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + builder.build(new OutputStreamWriter(bos)); + outputStream.write(bos.toByteArray()); + outputStream.flush(); + outputStream.close(); + return pipe[0]; + } catch (Exception e) { + throw new FileNotFoundException(e.getMessage()); + } + } + }; + setupProvider(TEST_PROVIDER_AUTHORITY, cp); return this; } /** - * Simulates an apk install with a default main activity with same class and package name - */ - public void installApp(String component) throws NameNotFoundException { - IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN); - filter.addCategory(Intent.CATEGORY_LAUNCHER); - installApp(component, component, filter); - } - - /** - * Simulates a custom shortcut install - */ - public void installCustomShortcut(String pkg, String clazz) throws NameNotFoundException { - installApp(pkg, clazz, new IntentFilter(ACTION_CREATE_SHORTCUT)); - } - - private void installApp(String pkg, String clazz, IntentFilter filter) - throws NameNotFoundException { - ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager()); - ComponentName cn = new ComponentName(pkg, clazz); - spm.addActivityIfNotPresent(cn); - - filter.addCategory(Intent.CATEGORY_DEFAULT); - spm.addIntentFilterForActivity(cn, filter); - } - - /** * Loads the model in memory synchronously */ public void loadModelSync() throws ExecutionException, InterruptedException { - // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread, - // so that we can wait appropriately for the loader to complete. - ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR); - sle.setHandler(Executors.UI_HELPER_EXECUTOR.getHandler()); - - Callbacks mockCb = mock(Callbacks.class); - getModel().addCallbacksAndLoad(mockCb); + Callbacks mockCb = new Callbacks() { }; + Executors.MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get(); Executors.MODEL_EXECUTOR.submit(() -> { }).get(); - Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get(); - - sle.setHandler(null); - getModel().removeCallbacks(mockCb); + Executors.MAIN_EXECUTOR.submit(() -> { }).get(); + Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get(); } /** @@ -437,4 +473,97 @@ public class LauncherModelHelper { return mOpenHelper; } } + + public static boolean deleteContents(File dir) { + File[] files = dir.listFiles(); + boolean success = true; + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + success &= deleteContents(file); + } + if (!file.delete()) { + success = false; + } + } + } + return success; + } + + public class SanboxModelContext extends SandboxContext { + + private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>(); + private final PackageManager mPm; + private final File mDbDir; + + SanboxModelContext() { + super(ApplicationProvider.getApplicationContext(), + UserCache.INSTANCE, InstallSessionHelper.INSTANCE, + LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, + DisplayController.INSTANCE, CustomWidgetManager.INSTANCE, + SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, + ItemInstallQueue.INSTANCE); + mPm = spy(getBaseContext().getPackageManager()); + mDbDir = new File(getCacheDir(), UUID.randomUUID().toString()); + } + + public SanboxModelContext allow(MainThreadInitializedObject object) { + mAllowedObjects.add(object); + return this; + } + + @Override + public File getDatabasePath(String name) { + if (!mDbDir.exists()) { + mDbDir.mkdirs(); + } + return new File(mDbDir, name); + } + + @Override + public ContentResolver getContentResolver() { + return mMockResolver; + } + + @Override + public void onDestroy() { + if (deleteContents(mDbDir)) { + mDbDir.delete(); + } + super.onDestroy(); + } + + + @Override + protected <T> T createObject(ObjectProvider<T> provider) { + return spy(provider.get(this)); + } + + @Override + public PackageManager getPackageManager() { + return mPm; + } + + @Override + public Object getSystemService(String name) { + Object service = mSpiedServices.get(name); + return service != null ? service : super.getSystemService(name); + } + + public <T> T spyService(Class<T> tClass) { + String name = getSystemServiceName(tClass); + Object service = mSpiedServices.get(name); + if (service != null) { + return (T) service; + } + + T result = spy(getSystemService(tClass)); + mSpiedServices.put(name, result); + return result; + } + } + + private static Context testContext() { + return getInstrumentation().getContext(); + } } diff --git a/tests/src/com/android/launcher3/util/ReflectionHelpers.java b/tests/src/com/android/launcher3/util/ReflectionHelpers.java new file mode 100644 index 0000000000..d89975de8a --- /dev/null +++ b/tests/src/com/android/launcher3/util/ReflectionHelpers.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import java.lang.reflect.Field; + +public class ReflectionHelpers { + + /** + * Reflectively get the value of a field. + * + * @param object Target object. + * @param fieldName The field name. + * @param <R> The return type. + * @return Value of the field on the object. + */ + public static <R> R getField(Object object, String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (R) field.get(object); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a field. + * + * @param object Target object. + * @param fieldName The field name. + * @param fieldNewValue New value. + */ + public static void setField(Object object, String fieldName, Object fieldNewValue) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} |