Merge "Revert "Revert "Migrating all model tests to Instrumentation tests""" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
667bda8448
+64
-64
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -162,7 +162,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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,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";
|
||||
|
||||
@@ -82,6 +82,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;
|
||||
@@ -118,6 +119,17 @@ public class DisplayController implements DisplayListener, ComponentCallbacks {
|
||||
return internalDisplays;
|
||||
}
|
||||
|
||||
@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) { }
|
||||
|
||||
@@ -165,6 +177,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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -81,6 +80,11 @@ public class SettingsCache extends ContentObserver {
|
||||
mResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+35
-9
@@ -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));
|
||||
|
||||
@@ -100,6 +108,24 @@ public class AddWorkspaceItemsTaskTest {
|
||||
.isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -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);
|
||||
+74
-37
@@ -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);
|
||||
}
|
||||
|
||||
+12
-8
@@ -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)));
|
||||
+13
-7
@@ -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);
|
||||
|
||||
+21
-24
@@ -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));
|
||||
+34
-35
@@ -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);
|
||||
+19
-13
@@ -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());
|
||||
|
||||
+58
-61
@@ -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());
|
||||
TestUtil.installDummyApp();
|
||||
mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, 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());
|
||||
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,9 +170,15 @@ 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;
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-7
@@ -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,
|
||||
+7
-4
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
+214
-85
@@ -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);
|
||||
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
|
||||
"settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY);
|
||||
ContentProvider cp = new TestInformationProvider() {
|
||||
|
||||
shadowOf(context.getPackageManager())
|
||||
.addProviderIfNotPresent(new ComponentName("com.test", "Mock")).authority =
|
||||
TEST_PROVIDER_AUTHORITY;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user