Merge "Removing support for lagacy shortcuts" into udc-qpr-dev

This commit is contained in:
TreeHugger Robot
2023-05-17 19:25:45 +00:00
committed by Android (Google) Code Review
36 changed files with 470 additions and 760 deletions
@@ -337,7 +337,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
if (getTag() instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
isBadged = !Process.myUserHandle().equals(info.user)
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
}
@@ -25,7 +25,6 @@ import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
@@ -283,7 +282,6 @@ public class QuickstepLauncher extends Launcher {
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
|| info.itemType == ITEM_TYPE_SHORTCUT
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
@@ -1162,7 +1160,6 @@ public class QuickstepLauncher extends Launcher {
}
switch (info.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPWIDGET:
// Fall through and continue if it's an app, shortcut, or widget
@@ -20,6 +20,7 @@ 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.TestUtil.runOnExecutorSync;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
@@ -40,11 +41,9 @@ import android.text.TextUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.LauncherAppState;
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.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -53,8 +52,6 @@ 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 java.util.Arrays;
import java.util.List;
@@ -76,17 +73,9 @@ public final class WidgetsPredicationUpdateTaskTest {
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
@Mock
private IconCache mIconCache;
@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());
mUserHandle = myUserHandle();
mApp1Provider1 = createAppWidgetProviderInfo(
@@ -114,16 +103,12 @@ public final class WidgetsPredicationUpdateTaskTest {
.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");
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
.atWorkspace(0, 1, 2).putWidget("app4", "provider1", 1, 1)
.atWorkspace(0, 1, 3).putWidget("app5", "provider1", 1, 1);
mModelHelper.setupDefaultLayoutProvider(builder);
MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get();
MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update(
LauncherAppState.getInstance(mModelHelper.sandboxContext),
/* packageUser= */ null));
MODEL_EXECUTOR.submit(() -> { }).get();
MAIN_EXECUTOR.submit(() -> { }).get();
mModelHelper.loadModelSync();
}
@After
@@ -132,65 +117,72 @@ public final class WidgetsPredicationUpdateTaskTest {
}
@Test
public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()
throws Exception {
// WHEN newPredicationTask is executed with app predication of 5 apps.
AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
mUserHandle);
AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
mUserHandle);
AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
mUserHandle);
AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
mUserHandle);
AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mUserHandle);
mModelHelper.executeTaskForTest(
newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)))
.forEach(Runnable::run);
public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
// WHEN newPredicationTask is executed with app predication of 5 apps.
AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
mUserHandle);
AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
mUserHandle);
AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
mUserHandle);
AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
mUserHandle);
AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mUserHandle);
mCallback.mRecommendedWidgets = null;
mModelHelper.getModel().enqueueModelUpdateTask(
newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)));
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
// THEN only 2 widgets are returned because
// 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
// excluded from the result.
// 2. app3 doesn't have a widget.
// 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2);
assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
// THEN only 2 widgets are returned because
// 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
// excluded from the result.
// 2. app3 doesn't have a widget.
// 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2);
assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
});
}
@Test
public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty()
throws Exception {
public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty() {
runOnExecutorSync(MODEL_EXECUTOR, () -> {
// Not installed widget
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
mUserHandle);
// Not installed app
AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
mUserHandle);
// Workspace added widgets
AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
mUserHandle);
AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mUserHandle);
mModelHelper.executeTaskForTest(
newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)))
.forEach(Runnable::run);
// Not installed widget
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
mUserHandle);
// Not installed app
AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
mUserHandle);
// Workspace added widgets
AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
mUserHandle);
AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mUserHandle);
// THEN only 2 widgets are returned because the launcher only filters out non-exist widgets.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2);
// Another widget from the same package
assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
mCallback.mRecommendedWidgets = null;
mModelHelper.getModel().enqueueModelUpdateTask(
newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)));
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
// THEN only 2 widgets are returned because the launcher only filters out
// non-exist widgets.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2);
// Another widget from the same package
assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
});
}
private void assertWidgetInfo(
+1 -2
View File
@@ -1928,7 +1928,7 @@ public class Launcher extends StatefulActivity<LauncherState>
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
addAppWidgetFromDrop((PendingAddWidgetInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
processShortcutFromDrop((PendingAddShortcutInfo) info);
break;
default:
@@ -2435,7 +2435,6 @@ public class Launcher extends StatefulActivity<LauncherState>
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
WorkspaceItemInfo info = (WorkspaceItemInfo) item;
view = createShortcut(info);
@@ -104,6 +104,8 @@ public class LauncherAppState implements SafeCloseable {
});
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
mOnTerminateCallback.add(() ->
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel));
SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
@@ -123,8 +125,9 @@ public class LauncherAppState implements SafeCloseable {
mOnTerminateCallback.add(userChangeListener::close);
LockedUserState.get(context).runOnUserUnlocked(() -> {
CustomWidgetManager.INSTANCE.get(mContext)
.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
IconObserver observer = new IconObserver();
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
@@ -159,6 +162,7 @@ public class LauncherAppState implements SafeCloseable {
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
}
private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -180,9 +184,6 @@ public class LauncherAppState implements SafeCloseable {
*/
@Override
public void close() {
mModel.destroy();
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
mOnTerminateCallback.executeAllAndDestroy();
}
@@ -89,7 +89,9 @@ public class LauncherSettings {
/**
* The gesture is an application created shortcut
* @deprecated This is no longer supported. Use {@link #ITEM_TYPE_DEEP_SHORTCUT} instead
*/
@Deprecated
public static final int ITEM_TYPE_SHORTCUT = 1;
/**
@@ -213,7 +215,6 @@ public class LauncherSettings {
public static final String itemTypeToString(int type) {
switch(type) {
case ITEM_TYPE_APPLICATION: return "APP";
case ITEM_TYPE_SHORTCUT: return "SHORTCUT";
case ITEM_TYPE_FOLDER: return "FOLDER";
case ITEM_TYPE_APPWIDGET: return "WIDGET";
case ITEM_TYPE_CUSTOM_APPWIDGET: return "CUSTOMWIDGET";
+2 -4
View File
@@ -1840,7 +1840,6 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
return (aboveShortcut && willBecomeShortcut);
@@ -2759,7 +2758,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
boolean findNearestVacantCell = true;
if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
@@ -2832,8 +2831,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
View view;
switch (info.itemType) {
case ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
if (info instanceof WorkspaceItemFactory) {
@@ -881,7 +881,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT));
}
@@ -260,7 +260,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
item != mInfo && !mFolder.isOpen());
}
@@ -34,13 +34,9 @@ import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -78,8 +74,6 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -183,7 +177,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
private final DeviceProfile mDp;
private final DeviceProfile mDpOrig;
private final Rect mInsets;
private final WorkspaceItemInfo mWorkspaceItemInfo;
private final LayoutInflater mHomeElementInflater;
private final InsettableFrameLayout mRootView;
private final Hotseat mHotseat;
@@ -221,19 +214,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom());
mDp.updateInsets(mInsets);
BaseIconFactory iconFactory =
new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(
new AdaptiveIconDrawable(
new ColorDrawable(Color.WHITE),
new ColorDrawable(Color.WHITE)));
mWorkspaceItemInfo = new WorkspaceItemInfo();
mWorkspaceItemInfo.bitmap = iconInfo;
mWorkspaceItemInfo.intent = new Intent();
mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
context.getString(R.string.label_application);
mHomeElementInflater = LayoutInflater.from(
new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
mHomeElementInflater.setFactory2(this);
@@ -483,7 +463,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
for (ItemInfo itemInfo : currentWorkspaceItems) {
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
break;
@@ -91,8 +91,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
List<ItemInfo> filteredItems = new ArrayList<>();
for (Pair<ItemInfo, Object> entry : mItemList) {
ItemInfo item = entry.first;
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
// Short-circuit this logic if the icon exists somewhere on the workspace
if (shortcutExists(dataModel, item.getIntent(), item.user)) {
continue;
@@ -210,7 +210,6 @@ public class BgDataModel {
// Fall through.
}
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
workspaceItems.remove(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -245,7 +244,6 @@ public class BgDataModel {
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
workspaceItems.add(item);
@@ -455,7 +455,6 @@ public class GridSizeMigrationUtil {
try {
// calculate weight
switch (entry.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
@@ -531,7 +530,6 @@ public class GridSizeMigrationUtil {
try {
// calculate weight
switch (entry.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
@@ -286,7 +286,6 @@ public class ItemInstallQueue {
final WorkspaceItemInfo si = new WorkspaceItemInfo();
si.user = user;
si.itemType = ITEM_TYPE_APPLICATION;
LauncherActivityInfo lai;
boolean usePackageIcon = laiList.isEmpty();
@@ -193,9 +193,7 @@ public class LoaderCursor extends CursorWrapper {
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
WorkspaceItemInfo wai, boolean useLowResIcon) {
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_SHORTCUT
|| itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
|| restoreFlag != 0
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
? getIconBlob() : null;
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
@@ -347,7 +345,6 @@ public class LoaderCursor extends CursorWrapper {
}
final WorkspaceItemInfo info = new WorkspaceItemInfo();
info.itemType = Favorites.ITEM_TYPE_APPLICATION;
info.user = user;
info.intent = newIntent;
@@ -503,7 +503,6 @@ public class LoaderTask implements Runnable {
boolean allowMissingTarget = false;
switch (c.itemType) {
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
Intent intent = c.parseIntent();
@@ -517,9 +516,8 @@ public class LoaderTask implements Runnable {
ComponentName cn = intent.getComponent();
String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
if (TextUtils.isEmpty(targetPkg)
&& c.itemType != Favorites.ITEM_TYPE_SHORTCUT) {
c.markDeleted("Only legacy shortcuts can have null package");
if (TextUtils.isEmpty(targetPkg)) {
c.markDeleted("Shortcuts can't have null package");
return;
}
@@ -498,7 +498,6 @@ public class ModelWriter {
modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
switch (modelItem.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_FOLDER:
if (!mBgDataModel.workspaceItems.contains(modelItem)) {
@@ -30,7 +30,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINE
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
@@ -87,7 +86,6 @@ public class ItemInfo {
/**
* One of {@link Favorites#ITEM_TYPE_APPLICATION},
* {@link Favorites#ITEM_TYPE_SHORTCUT},
* {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
* {@link Favorites#ITEM_TYPE_FOLDER},
* {@link Favorites#ITEM_TYPE_APP_PAIR},
@@ -361,13 +359,6 @@ public class ItemInfo {
})
.orElse(LauncherAtom.Shortcut.newBuilder()));
break;
case ITEM_TYPE_SHORTCUT:
itemBuilder
.setShortcut(nullableComponent
.map(component -> LauncherAtom.Shortcut.newBuilder()
.setShortcutName(component.flattenToShortString()))
.orElse(LauncherAtom.Shortcut.newBuilder()));
break;
case ITEM_TYPE_APPWIDGET:
itemBuilder
.setWidget(nullableComponent
@@ -96,7 +96,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
public WorkspaceItemInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
public WorkspaceItemInfo(WorkspaceItemInfo info) {
@@ -205,8 +205,8 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(
FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) {
if (cn == null && hasStatusFlag(
FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON)) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a empty componentName instead of adding additional
// check everywhere.
@@ -72,7 +72,7 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn
}
public int getItemType() {
return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
return LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
}
@Override
@@ -250,6 +250,11 @@ public class IntArray implements Cloneable, Iterable<Integer> {
return b.toString();
}
@Override
public String toString() {
return "IntArray [" + toConcatString() + "]";
}
public static IntArray fromConcatString(String concatString) {
StringTokenizer tokenizer = new StringTokenizer(concatString, ",");
int[] array = new int[tokenizer.countTokens()];
@@ -48,8 +48,8 @@ public class MainThreadInitializedObject<T> {
}
public T get(Context context) {
if (context instanceof SandboxContext) {
return ((SandboxContext) context).getObject(this, mProvider);
if (context instanceof SandboxContext sc) {
return sc.getObject(this);
}
if (mValue == null) {
@@ -131,24 +131,22 @@ public class MainThreadInitializedObject<T> {
* Find a cached object from mObjectMap if we have already created one. If not, generate
* an object using the provider.
*/
protected <T> T getObject(MainThreadInitializedObject<T> object,
ObjectProvider<T> provider) {
protected <T> T getObject(MainThreadInitializedObject<T> object) {
synchronized (mDestroyLock) {
if (mDestroyed) {
Log.e(TAG, "Static object access with a destroyed context");
}
T t = (T) mObjectMap.get(object);
if (t != null) {
return t;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
t = createObject(provider);
t = createObject(object);
// Check if we've explicitly allowed the object or if it's a SafeCloseable,
// it will get destroyed in onDestroy()
if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
throw new IllegalStateException(
"Leaking unknown objects " + object + " " + provider + " " + t);
throw new IllegalStateException("Leaking unknown objects "
+ object + " " + object.mProvider + " " + t);
}
mObjectMap.put(object, t);
mOrderedObjects.add(t);
@@ -157,15 +155,15 @@ public class MainThreadInitializedObject<T> {
}
try {
return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get();
return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@UiThread
protected <T> T createObject(ObjectProvider<T> provider) {
return provider.get(this);
protected <T> T createObject(MainThreadInitializedObject<T> object) {
return object.mProvider.get(this);
}
}
}
@@ -332,6 +332,12 @@ public interface ActivityContext {
return null;
}
boolean isShortcut = (item instanceof WorkspaceItemInfo)
&& item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& !((WorkspaceItemInfo) item).isPromise();
if (isShortcut && GO_DISABLE_WIDGETS) {
return null;
}
ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
: makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
@@ -343,13 +349,11 @@ public interface ActivityContext {
intent.setSourceBounds(Utilities.getViewBounds(v));
}
try {
boolean isShortcut = (item instanceof WorkspaceItemInfo)
&& (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& !((WorkspaceItemInfo) item).isPromise();
if (isShortcut) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
String id = ((WorkspaceItemInfo) item).getDeepShortcutId();
String packageName = intent.getPackage();
((Context) this).getSystemService(LauncherApps.class).startShortcut(
packageName, id, intent.getSourceBounds(), optsBundle, user);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
context.startActivity(intent, optsBundle);
@@ -424,55 +428,6 @@ public interface ActivityContext {
return new ActivityOptionsWrapper(options, new RunnableList());
}
/**
* Safely launches an intent for a shortcut.
*
* @param intent Intent to start.
* @param optsBundle Optional launch arguments.
* @param info Shortcut information.
*/
default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
try {
StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
try {
// Temporarily disable deathPenalty on all default checks. For eg, shortcuts
// containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
// is enabled by default on NYC.
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
.penaltyLog().build());
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
} else {
// Could be launching some bookkeeping activity
((Context) this).startActivity(intent, optsBundle);
}
} finally {
StrictMode.setVmPolicy(oldPolicy);
}
} catch (SecurityException e) {
throw e;
}
}
/**
* A wrapper around the platform method with Launcher specific checks.
*/
default void startShortcut(String packageName, String id, Rect sourceBounds,
Bundle startActivityOptions, UserHandle user) {
if (GO_DISABLE_WIDGETS) {
return;
}
try {
((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id,
sourceBounds, startActivityOptions, user);
} catch (SecurityException | IllegalStateException e) {
Log.e(TAG, "Failed to start shortcut", e);
}
}
default CellPosMapper getCellPosMapper() {
return CellPosMapper.DEFAULT;
}
@@ -290,7 +290,6 @@ public class OptionsPopupView extends ArrowPopup<Launcher>
static WorkspaceItemInfo placeholderInfo(Intent intent) {
WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
placeholderInfo.intent = intent;
placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
return placeholderInfo;
}
@@ -1,28 +0,0 @@
# Model data used by CacheDataUpdatedTaskTest
classMap s com.android.launcher3.model.data.WorkspaceItemInfo
# Items for the BgDataModel
# App shortcuts
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
# Auto install app shortcut
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
# Custom shortcuts
bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
# Restored custom shortcut
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
allApps componentName=app1/class1 intent=component=app1/class1
allApps componentName=app1/class2 intent=component=app1/class2
allApps componentName=app2/class1 intent=component=app2/class1
allApps componentName=app2/class2 intent=component=app2/class2
@@ -1,24 +0,0 @@
# Model data used by PackageInstallStateChangeTaskTest
classMap s com.android.launcher3.model.data.WorkspaceItemInfo
classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
# Items for the BgDataModel
# App shortcuts
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
# Promise icons for app3
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
# Promise icon for app4
bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
# Widget
bgItem w providerName=app4/provider1 id=9
bgItem w providerName=app5/provider1 id=10
@@ -1,24 +0,0 @@
# Model data used by WidgetsPredictionUpdateTasksTest
classMap s com.android.launcher3.model.data.WorkspaceItemInfo
classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
# Items for the BgDataModel
# App shortcuts
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
# Promise icons for app3
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
# Promise icon for app4
bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
# Widget
bgItem w providerName=app4/provider1 id=9
bgItem w providerName=app5/provider1 id=10
@@ -17,17 +17,18 @@ package com.android.launcher3.model
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.ContentWriter
import com.android.launcher3.util.GridOccupancy
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.IntSparseArrayMap
import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
import java.util.UUID
/** Base class for workspace related tests. */
@@ -38,6 +39,7 @@ abstract class AbstractWorkspaceModelTest {
val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
}
protected lateinit var mLayoutBuilder: LauncherLayoutBuilder
protected lateinit var mTargetContext: Context
protected lateinit var mIdp: InvariantDeviceProfile
protected lateinit var mAppState: LauncherAppState
@@ -47,6 +49,7 @@ abstract class AbstractWorkspaceModelTest {
protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
open fun setup() {
mLayoutBuilder = LauncherLayoutBuilder()
mModelHelper = LauncherModelHelper()
mTargetContext = mModelHelper.sandboxContext
mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
@@ -64,10 +67,11 @@ abstract class AbstractWorkspaceModelTest {
/** Sets up workspaces with the given screen IDs with some items and a 2x2 space. */
fun setupWorkspaces(screenIdsWithItems: List<Int>) {
var nextItemId = 1
screenIdsWithItems.forEach { screenId ->
nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces)
}
screenIdsWithItems.forEach { screenId -> setupWorkspace(screenId, nonEmptyScreenSpaces) }
mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
mIdp.numRows = 5
mIdp.numColumns = mIdp.numRows
mModelHelper.loadModelSync()
}
/**
@@ -78,30 +82,23 @@ abstract class AbstractWorkspaceModelTest {
screen1: List<Rect>? = null,
screen2: List<Rect>? = null,
screen3: List<Rect>? = null,
) = listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
) {
listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
mIdp.numRows = 5
mIdp.numColumns = mIdp.numRows
mModelHelper.loadModelSync()
}
private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
var nextItemId = 1
workspaceSpaces.forEachIndexed { screenId, spaces ->
if (spaces != null) {
nextItemId = setupWorkspace(nextItemId, screenId, spaces)
setupWorkspace(screenId, spaces)
}
}
}
private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int {
return mModelHelper.executeSimpleTask { dataModel ->
writeWorkspaceWithSpaces(dataModel, startId, screenId, spaces)
}
}
private fun writeWorkspaceWithSpaces(
bgDataModel: BgDataModel,
itemStartId: Int,
screenId: Int,
spaces: List<Rect>,
): Int {
var itemId = itemStartId
private fun setupWorkspace(screenId: Int, spaces: List<Rect>) {
val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
spaces.forEach { spaceRect -> occupancy.markCells(spaceRect, false) }
@@ -109,35 +106,22 @@ abstract class AbstractWorkspaceModelTest {
mScreenOccupancy.append(screenId, occupancy)
for (x in 0 until mIdp.numColumns) {
for (y in 0 until mIdp.numRows) {
if (!occupancy.cells[x][y]) {
continue
if (occupancy.cells[x][y]) {
mLayoutBuilder.atWorkspace(x, y, screenId).putApp(TEST_PACKAGE, TEST_ACTIVITY)
}
val info = getExistingItem()
info.id = itemId++
info.screenId = screenId
info.cellX = x
info.cellY = y
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
bgDataModel.addItem(mTargetContext, info, false)
val writer = ContentWriter(mTargetContext)
info.writeToValues(writer)
writer.put(LauncherSettings.Favorites._ID, info.id)
mTargetContext.contentResolver.insert(
LauncherSettings.Favorites.CONTENT_URI,
writer.getValues(mTargetContext)
)
}
}
return itemId
}
fun getExistingItem() =
WorkspaceItemInfo().apply { intent = Intent().setComponent(ComponentName("a", "b")) }
WorkspaceItemInfo().apply {
intent = AppInfo.makeLaunchIntent(ComponentName(TEST_PACKAGE, TEST_ACTIVITY))
}
fun getNewItem(): WorkspaceItemInfo {
val itemPackage = UUID.randomUUID().toString()
return WorkspaceItemInfo().apply {
intent = Intent().setComponent(ComponentName(itemPackage, itemPackage))
intent = AppInfo.makeLaunchIntent(ComponentName(itemPackage, itemPackage))
}
}
}
@@ -23,7 +23,7 @@ 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.IntArray
import com.android.launcher3.util.IntSet
import com.android.launcher3.util.TestUtil.runOnExecutorSync
import com.android.launcher3.util.any
import com.android.launcher3.util.eq
import com.android.launcher3.util.same
@@ -32,8 +32,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -46,11 +44,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
@Captor private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
@Captor private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
@Mock private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
private lateinit var mDataModelCallbacks: MyCallbacks
@Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
@@ -58,7 +52,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
override fun setup() {
super.setup()
MockitoAnnotations.initMocks(this)
whenever(mDataModelCallbacks.getPagesToBindSynchronously(any())).thenReturn(IntSet())
mDataModelCallbacks = MyCallbacks()
Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
.get()
}
@@ -105,7 +99,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
assertThat(addedItems.size).isEqualTo(0)
verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks)
verifyZeroInteractions(mWorkspaceItemSpaceFinder)
}
@Test
@@ -191,22 +185,14 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
): List<AddedItem> {
setupWorkspaces(nonEmptyScreenIds)
val task = newTask(*itemsToAdd)
var updateCount = 0
mModelHelper.executeTaskForTest(task).forEach {
updateCount++
it.run()
}
val addedItems = mutableListOf<AddedItem>()
if (updateCount > 0) {
verify(mDataModelCallbacks)
.bindAppsAdded(
any(),
mNotAnimatedItemArgumentCaptor.capture(),
mAnimatedItemArgumentCaptor.capture()
)
addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
runOnExecutorSync(Executors.MODEL_EXECUTOR) {
mDataModelCallbacks.addedItems.clear()
mModelHelper.model.enqueueModelUpdateTask(task)
runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
addedItems.addAll(mDataModelCallbacks.addedItems)
}
return addedItems
@@ -224,3 +210,17 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
}
private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean)
private class MyCallbacks : BgDataModel.Callbacks {
val addedItems = mutableListOf<AddedItem>()
override fun bindAppsAdded(
newScreens: IntArray?,
addNotAnimated: ArrayList<ItemInfo>,
addAnimated: ArrayList<ItemInfo>
) {
addedItems.addAll(addAnimated.map { AddedItem(it, true) })
addedItems.addAll(addNotAnimated.map { AddedItem(it, false) })
}
}
@@ -1,32 +1,31 @@
package com.android.launcher3.model;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.os.Process;
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;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.PackageUserKey;
import org.junit.After;
import org.junit.Before;
@@ -35,6 +34,7 @@ import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
/**
* Tests for {@link CacheDataUpdatedTask}
@@ -43,49 +43,40 @@ import java.util.HashSet;
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
private static final String NEW_LABEL_PREFIX = "new-label-";
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
private LauncherModelHelper mModelHelper;
private Context mContext;
private int mSession1;
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
mModelHelper.initializeData("cache_data_updated_task_data");
mContext = mModelHelper.sandboxContext;
mSession1 = mModelHelper.createInstallerSession(PENDING_APP_1);
mModelHelper.createInstallerSession(PENDING_APP_2);
// Add placeholder entries in the cache to simulate update
Context context = mModelHelper.sandboxContext;
IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
@Override
@NonNull
public ComponentName getComponent(@NonNull ItemInfo info) {
return info.getTargetComponent();
}
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
.atHotseat(1).putFolder("MyFolder")
.addApp(TEST_PACKAGE, TEST_ACTIVITY) // 2
.addApp(TEST_PACKAGE, TEST_ACTIVITY2) // 3
.addApp(TEST_PACKAGE, TEST_ACTIVITY3) // 4
@NonNull
@Override
public UserHandle getUser(@NonNull ItemInfo info) {
return info.user;
}
// Pending App 1
.addApp(PENDING_APP_1, TEST_ACTIVITY) // 5
.addApp(PENDING_APP_1, TEST_ACTIVITY2) // 6
.addApp(PENDING_APP_1, TEST_ACTIVITY3) // 7
@NonNull
@Override
public CharSequence getLabel(@NonNull ItemInfo info) {
return NEW_LABEL_PREFIX + info.id;
}
@NonNull
@Override
public BitmapInfo loadIcon(@NonNull Context context, @NonNull ItemInfo info) {
return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED);
}
};
UserManager um = context.getSystemService(UserManager.class);
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
iconCache.addIconToDBAndMemCache(info, placeholderLogic, new PackageInfo(),
um.getSerialNumberForUser(info.user), true);
}
// Pending App 2
.addApp(PENDING_APP_2, TEST_ACTIVITY) // 8
.addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
.addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10
.build();
mModelHelper.setupDefaultLayoutProvider(builder);
mModelHelper.loadModelSync();
assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
}
@After
@@ -94,27 +85,63 @@ public class CacheDataUpdatedTaskTest {
}
private CacheDataUpdatedTask newTask(int op, String... pkg) {
return new CacheDataUpdatedTask(op, Process.myUserHandle(),
return new CacheDataUpdatedTask(op, myUserHandle(),
new HashSet<>(Arrays.asList(pkg)));
}
@Test
public void testCacheUpdate_update_apps() throws Exception {
// Clear all icons from apps list so that its easy to check what was updated
for (AppInfo info : mModelHelper.getAllAppsList().data) {
info.bitmap = BitmapInfo.LOW_RES_INFO;
}
public void testCacheUpdate_update_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
// Clear all icons from apps list so that its easy to check what was updated
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
mModelHelper.getModel().enqueueModelUpdateTask(
newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, TEST_PACKAGE));
// Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
// is not updated
verifyUpdate(1, 2);
// Verify that only the app icons of TEST_PACKAGE (id 2, 3, 4) are updated.
verifyUpdate(2, 3, 4);
});
}
// Verify that only app1 var updated in allAppsList
assertFalse(mModelHelper.getAllAppsList().data.isEmpty());
for (AppInfo info : mModelHelper.getAllAppsList().data) {
if (info.componentName.getPackageName().equals("app1")) {
@Test
public void testSessionUpdate_ignores_normal_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
// Clear all icons from apps list so that its easy to check what was updated
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
mModelHelper.getModel().enqueueModelUpdateTask(
newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, TEST_PACKAGE));
// TEST_PACKAGE has no restored shortcuts. Verify that nothing was updated.
verifyUpdate();
});
}
@Test
public void testSessionUpdate_updates_pending_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache(
new PackageUserKey(PENDING_APP_1, myUserHandle()),
mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession1));
// Clear all icons from apps list so that its easy to check what was updated
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
mModelHelper.getModel().enqueueModelUpdateTask(
newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, PENDING_APP_1));
// Only restored apps from PENDING_APP_1 (id 5, 6, 7) are updated
verifyUpdate(5, 6, 7);
});
}
private void verifyUpdate(int... idsUpdated) {
IntSet updates = IntSet.wrap(idsUpdated);
for (WorkspaceItemInfo info : allItems()) {
if (updates.contains(info.id)) {
assertFalse(info.bitmap.isNullOrLowRes());
} else {
assertTrue(info.bitmap.isNullOrLowRes());
@@ -122,33 +149,7 @@ public class CacheDataUpdatedTaskTest {
}
}
@Test
public void testSessionUpdate_ignores_normal_apps() throws Exception {
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
// app1 has no restored shortcuts. Verify that nothing was updated.
verifyUpdate();
}
@Test
public void testSessionUpdate_updates_pending_apps() throws Exception {
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
// app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
// were updated
verifyUpdate(5, 6);
}
private void verifyUpdate(Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (updates.contains(info.id)) {
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
} else {
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
}
}
private List<WorkspaceItemInfo> allItems() {
return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
}
}
@@ -24,7 +24,6 @@ import android.graphics.Point
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
@@ -108,8 +107,8 @@ class GridSizeMigrationUtilTest {
fun testMigration() {
// Src Hotseat icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Src grid icons
// _ _ _ _ _
@@ -124,7 +123,7 @@ class GridSizeMigrationUtilTest {
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
// Dest hotseat icons
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
// Dest grid icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
@@ -219,8 +218,8 @@ class GridSizeMigrationUtilTest {
// Hotseat items in grid A
// 1 2 _ 3 4
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Workspace items in grid A
// _ _ _ _ _
@@ -235,7 +234,7 @@ class GridSizeMigrationUtilTest {
// Hotseat items in grid B
// 2 _ _ _
addItem(ITEM_TYPE_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
// Workspace items in grid B
// _ _ _ _
// _ _ _ 10
@@ -291,7 +290,7 @@ class GridSizeMigrationUtilTest {
null
)
?: throw IllegalStateException()
var locMap = parseLocMap(context, c)
var locMap = parseLocMap(c)
// Expected items in grid B
// _ _ _ _
// 5 6 7 8
@@ -348,7 +347,7 @@ class GridSizeMigrationUtilTest {
null
)
?: throw IllegalStateException()
locMap = parseLocMap(context, c)
locMap = parseLocMap(c)
// Expected workspace items in grid A
// _ _ _ _ _
// _ _ _ _ 5
@@ -410,7 +409,7 @@ class GridSizeMigrationUtilTest {
null
)
?: throw IllegalStateException()
locMap = parseLocMap(context, c)
locMap = parseLocMap(c)
// Expected workspace items in grid B
// _ _ _ _
// 5 6 _ 8
@@ -436,7 +435,7 @@ class GridSizeMigrationUtilTest {
c.close()
}
private fun parseLocMap(context: Context, c: Cursor): Map<String, Triple<Int, Int, Int>> {
private fun parseLocMap(c: Cursor): Map<String, Triple<Int, Int, Int>> {
// Check workspace items
val intentIndex = c.getColumnIndex(INTENT)
val screenIndex = c.getColumnIndex(SCREEN)
@@ -465,7 +464,16 @@ class GridSizeMigrationUtilTest {
1,
TMP_TABLE
),
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE),
addItem(
ITEM_TYPE_DEEP_SHORTCUT,
1,
CONTAINER_HOTSEAT,
0,
0,
testPackage2,
2,
TMP_TABLE
),
addItem(
ITEM_TYPE_APPLICATION,
2,
@@ -476,7 +484,16 @@ class GridSizeMigrationUtilTest {
3,
TMP_TABLE
),
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
addItem(
ITEM_TYPE_DEEP_SHORTCUT,
3,
CONTAINER_HOTSEAT,
0,
0,
testPackage4,
4,
TMP_TABLE
)
)
val numSrcDatabaseHotseatIcons = srcHotseatItems.size
idp.numDatabaseHotseatIcons = 6
@@ -532,9 +549,9 @@ class GridSizeMigrationUtilTest {
@Test
fun migrateFromLargerHotseat() {
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
addItem(ITEM_TYPE_DEEP_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
idp.numDatabaseHotseatIcons = 4
@@ -30,7 +30,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ICON;
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.OPTIONS;
import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
import static com.android.launcher3.LauncherSettings.Favorites.RANK;
@@ -158,13 +158,13 @@ public class LoaderCursorTest {
@Test
public void loadSimpleShortcut() {
initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user));
assertEquals("my-shortcut", info.title);
assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
assertEquals(ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
}
@Test
@@ -1,5 +1,12 @@
package com.android.launcher3.model;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.junit.Assert.assertEquals;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -9,6 +16,8 @@ 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.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import org.junit.After;
@@ -16,9 +25,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.HashSet;
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
@@ -26,12 +32,36 @@ import java.util.HashSet;
@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest {
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
private LauncherModelHelper mModelHelper;
private IntSet mDownloadingApps;
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
mModelHelper.initializeData("package_install_state_change_task_data");
mModelHelper.createInstallerSession(PENDING_APP_1);
mModelHelper.createInstallerSession(PENDING_APP_2);
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
.atWorkspace(0, 0, 1).putApp(TEST_PACKAGE, TEST_ACTIVITY) // 1
.atWorkspace(0, 0, 2).putApp(TEST_PACKAGE, TEST_ACTIVITY2) // 2
.atWorkspace(0, 0, 3).putApp(TEST_PACKAGE, TEST_ACTIVITY3) // 3
.atWorkspace(0, 0, 4).putApp(PENDING_APP_1, TEST_ACTIVITY) // 4
.atWorkspace(0, 0, 5).putApp(PENDING_APP_1, TEST_ACTIVITY2) // 5
.atWorkspace(0, 0, 6).putApp(PENDING_APP_1, TEST_ACTIVITY3) // 6
.atWorkspace(0, 0, 7).putWidget(PENDING_APP_1, "pending.widget", 1, 1) // 7
.atWorkspace(0, 0, 8).putApp(PENDING_APP_2, TEST_ACTIVITY) // 8
.atWorkspace(0, 0, 9).putApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
.atWorkspace(0, 0, 10).putApp(PENDING_APP_2, TEST_ACTIVITY3); // 10
mDownloadingApps = IntSet.wrap(4, 5, 6, 7, 8, 9, 10);
mModelHelper.setupDefaultLayoutProvider(builder);
mModelHelper.loadModelSync();
assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
}
@After
@@ -47,36 +77,45 @@ public class PackageInstallStateChangedTaskTest {
}
@Test
public void testSessionUpdate_ignore_installed() throws Exception {
mModelHelper.executeTaskForTest(newTask("app1", 30));
public void testSessionUpdate_ignore_installed() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
mModelHelper.getModel().enqueueModelUpdateTask(newTask(TEST_PACKAGE, 30));
// No shortcuts were updated
verifyProgressUpdate(0);
// No shortcuts were updated
verifyProgressUpdate(0);
});
}
@Test
public void testSessionUpdate_shortcuts_updated() throws Exception {
mModelHelper.executeTaskForTest(newTask("app3", 30));
public void testSessionUpdate_shortcuts_updated() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_1, 30));
verifyProgressUpdate(30, 5, 6, 7);
verifyProgressUpdate(30, 4, 5, 6, 7);
});
}
@Test
public void testSessionUpdate_widgets_updated() throws Exception {
mModelHelper.executeTaskForTest(newTask("app4", 30));
public void testSessionUpdate_widgets_updated() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_2, 30));
verifyProgressUpdate(30, 8, 9);
verifyProgressUpdate(30, 8, 9, 10);
});
}
private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
private void verifyProgressUpdate(int progress, int... idsUpdated) {
IntSet updates = IntSet.wrap(idsUpdated);
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (info instanceof WorkspaceItemInfo) {
assertEquals(updates.contains(info.id) ? progress: 100,
((WorkspaceItemInfo) info).getProgressLevel());
int expectedProgress = updates.contains(info.id) ? progress
: (mDownloadingApps.contains(info.id) ? 0 : 100);
if (info instanceof WorkspaceItemInfo wi) {
assertEquals(expectedProgress, wi.getProgressLevel());
} else {
assertEquals(updates.contains(info.id) ? progress: -1,
((LauncherAppWidgetInfo) info).installProgress);
assertEquals(expectedProgress, ((LauncherAppWidgetInfo) info).installProgress);
}
}
}
@@ -87,9 +87,8 @@ class DisplayControllerTest {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE), any()))
.thenReturn(windowManagerProxy)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE), any())).thenReturn(launcherPrefs)
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
// Mock WindowManagerProxy
val displayInfo =
@@ -15,34 +15,31 @@
*/
package com.android.launcher3.util;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
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 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.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
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;
@@ -57,13 +54,10 @@ import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherProvider;
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.testing.TestInformationProvider;
@@ -72,37 +66,25 @@ import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import org.mockito.ArgumentCaptor;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.IOException;
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;
/**
* Utility class to help manage Launcher Model and related objects for test.
*/
public class LauncherModelHelper {
public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
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 = testContext().getPackageName();
public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3";
public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4";
// Authority for providing a test default-workspace-layout data.
private static final String TEST_PROVIDER_AUTHORITY =
@@ -110,39 +92,18 @@ public class LauncherModelHelper {
private static final int DEFAULT_BITMAP_SIZE = 10;
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;
public final SandboxModelContext sandboxContext;
public final long defaultProfileId;
private final RunnableList mDestroyTask = new RunnableList();
private BgDataModel mDataModel;
private AllAppsList mAllAppsList;
public LauncherModelHelper() {
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 SandboxModelContext();
defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
.getSerialNumberForUser(Process.myUserHandle());
setupProvider(LauncherProvider.AUTHORITY, provider);
}
public 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());
sandboxContext.setupProvider(authority, provider);
}
public LauncherModel getModel() {
@@ -151,16 +112,35 @@ public class LauncherModelHelper {
public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) {
mDataModel = ReflectionHelpers.getField(getModel(), "mBgDataModel");
getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
@Override
public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
@NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
@NonNull Executor uiExecutor) {
mDataModel = dataModel;
}
@Override
public void run() { }
});
}
return mDataModel;
}
public synchronized AllAppsList getAllAppsList() {
if (mAllAppsList == null) {
mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList");
}
return mAllAppsList;
/**
* Creates a installer session for the provided package.
*/
public int createInstallerSession(String pkg) throws IOException {
SessionParams sp = new SessionParams(MODE_FULL_INSTALL);
sp.setAppPackageName(pkg);
Bitmap icon = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
icon.eraseColor(Color.RED);
sp.setAppIcon(icon);
sp.setAppLabel(pkg);
PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
int sessionId = pi.createSession(sp);
mDestroyTask.add(() -> pi.abandonSession(sessionId));
return sessionId;
}
public void destroy() {
@@ -175,6 +155,8 @@ public class LauncherModelHelper {
waitOrThrow(l1);
sandboxContext.onDestroy();
l2.countDown();
mDestroyTask.executeAllAndDestroy();
}
private void waitOrThrow(CountDownLatch latch) {
@@ -185,184 +167,6 @@ public class LauncherModelHelper {
}
}
/**
* Synchronously executes the task and returns all the UI callbacks posted.
*/
public List<Runnable> executeTaskForTest(ModelUpdateTask task) throws Exception {
LauncherModel model = getModel();
if (!model.isModelLoaded()) {
ReflectionHelpers.setField(model, "mModelLoaded", true);
}
Executor mockExecutor = mock(Executor.class);
model.enqueueModelUpdateTask(new ModelUpdateTask() {
@Override
public void init(@NonNull final LauncherAppState app,
@NonNull final LauncherModel model, @NonNull final BgDataModel dataModel,
@NonNull final AllAppsList allAppsList, @NonNull final Executor uiExecutor) {
task.init(app, model, dataModel, allAppsList, mockExecutor);
}
@Override
public void run() {
task.run();
}
});
MODEL_EXECUTOR.submit(() -> null).get();
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
verify(mockExecutor, atLeast(0)).execute(captor.capture());
return captor.getAllValues();
}
/**
* Synchronously executes a task on the model
*/
public <T> T executeSimpleTask(Function<BgDataModel, T> task) throws Exception {
BgDataModel dataModel = getBgDataModel();
return MODEL_EXECUTOR.submit(() -> task.apply(dataModel)).get();
}
/**
* Initializes mock data for the test.
*/
public void initializeData(String resourceName) throws Exception {
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(
resources.openRawResource(resId)))) {
String line;
HashMap<String, Class> classMap = new HashMap<>();
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.isEmpty()) {
continue;
}
String[] commands = line.split(" ");
switch (commands[0]) {
case "classMap":
classMap.put(commands[1], Class.forName(commands[2]));
break;
case "bgItem":
bgDataModel.addItem(sandboxContext,
(ItemInfo) initItem(classMap.get(commands[1]), commands, 2),
false);
break;
case "allApps":
allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}).get();
}
private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
HashMap<String, Field> cache = mFieldCache.get(clazz);
if (cache == null) {
cache = new HashMap<>();
Class c = clazz;
while (c != null) {
for (Field f : c.getDeclaredFields()) {
f.setAccessible(true);
cache.put(f.getName(), f);
}
c = c.getSuperclass();
}
mFieldCache.put(clazz, cache);
}
Object item = clazz.newInstance();
for (int i = startIndex; i < fieldDef.length; i++) {
String[] fieldData = fieldDef[i].split("=", 2);
Field f = cache.get(fieldData[0]);
Class type = f.getType();
if (type == int.class || type == long.class) {
f.set(item, Integer.parseInt(fieldData[1]));
} else if (type == CharSequence.class || type == String.class) {
f.set(item, fieldData[1]);
} else if (type == Intent.class) {
if (!fieldData[1].startsWith("#Intent")) {
fieldData[1] = "#Intent;" + fieldData[1] + ";end";
}
f.set(item, Intent.parseUri(fieldData[1], 0));
} else if (type == ComponentName.class) {
f.set(item, ComponentName.unflattenFromString(fieldData[1]));
} else {
throw new Exception("Added parsing logic for "
+ f.getName() + " of type " + f.getType());
}
}
return item;
}
public int addItem(int type, int screen, int container, int x, int y) {
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) {
return addItem(type, screen, container, x, y, profileId, TEST_PACKAGE);
}
public int addItem(int type, int screen, int container, int x, int y, String 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, defaultProfileId, packageName, id, contentUri);
return id;
}
/**
* Adds a mock item in the DB.
* @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
* folder (where the type represents the number of items in the folder).
*/
public int addItem(int type, int screen, int container, int x, int y, long profileId,
String packageName) {
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);
return id;
}
public void addItem(int type, int screen, int container, int x, int y, long profileId,
String packageName, int id, Uri contentUri) {
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, id);
values.put(LauncherSettings.Favorites.CONTAINER, container);
values.put(LauncherSettings.Favorites.SCREEN, screen);
values.put(LauncherSettings.Favorites.CELLX, x);
values.put(LauncherSettings.Favorites.CELLY, y);
values.put(LauncherSettings.Favorites.SPANX, 1);
values.put(LauncherSettings.Favorites.SPANY, 1);
values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
if (type == APP_ICON || type == SHORTCUT) {
values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
values.put(LauncherSettings.Favorites.INTENT,
new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
} else {
values.put(LauncherSettings.Favorites.ITEM_TYPE,
LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
// Add folder items.
for (int i = 0; i < type; i++) {
addItem(APP_ICON, 0, id, 0, 0, profileId);
}
}
sandboxContext.getContentResolver().insert(contentUri, values);
}
/**
* Sets up a mock provider to load the provided layout by default, next time the layout loads
*/
@@ -394,6 +198,9 @@ public class LauncherModelHelper {
}
};
setupProvider(TEST_PROVIDER_AUTHORITY, cp);
mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"settings delete secure launcher3.layout.provider")));
return this;
}
@@ -402,46 +209,16 @@ public class LauncherModelHelper {
*/
public void loadModelSync() throws ExecutionException, InterruptedException {
Callbacks mockCb = new Callbacks() { };
Executors.MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
Executors.MAIN_EXECUTOR.submit(() -> { }).get();
Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
MAIN_EXECUTOR.submit(() -> { }).get();
MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
}
/**
* An extension of LauncherProvider backed up by in-memory database.
*/
public static class TestLauncherProvider extends LauncherProvider {
@Override
public boolean onCreate() {
return true;
}
public SQLiteDatabase getDb() {
return getModelDbController().getDb();
}
}
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 SandboxModelContext extends SandboxContext {
public static class SandboxModelContext extends SandboxContext {
private final MockContentResolver mMockResolver = new MockContentResolver();
private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
private final PackageManager mPm;
private final File mDbDir;
@@ -453,8 +230,31 @@ public class LauncherModelHelper {
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
// System settings cache content provider. Ensure that they are statically initialized
Settings.Secure.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
Settings.System.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
Settings.Global.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
setupProvider(LauncherProvider.AUTHORITY, new LauncherProvider() {
@Override
public boolean onCreate() {
return true;
}
});
}
@Override
protected <T> T createObject(MainThreadInitializedObject<T> object) {
if (object == LauncherAppState.INSTANCE) {
return (T) new LauncherAppState(this, null /* iconCacheFileName */);
}
return super.createObject(object);
}
public SandboxModelContext allow(MainThreadInitializedObject object) {
@@ -505,9 +305,30 @@ public class LauncherModelHelper {
mSpiedServices.put(name, result);
return result;
}
}
private static Context testContext() {
return getInstrumentation().getContext();
public void setupProvider(String authority, ContentProvider provider) {
ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = authority;
providerInfo.applicationInfo = getApplicationInfo();
provider.attachInfo(this, providerInfo);
mMockResolver.addProvider(providerInfo.authority, provider);
doReturn(providerInfo).when(mPm).resolveContentProvider(eq(authority), anyInt());
}
private 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;
}
}
}
@@ -53,6 +53,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
@@ -154,6 +155,30 @@ public class TestUtil {
Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null);
}
/**
* Utility method to run a task synchronously which converts any exceptions to RuntimeException
*/
public static void runOnExecutorSync(ExecutorService executor, UncheckedRunnable task) {
try {
executor.submit(() -> {
try {
task.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** Interface to indicate a runnable which can throw any exception. */
public interface UncheckedRunnable {
/** Method to run the task */
void run() throws Exception;
}
private static class PackageInstallCheck extends LauncherApps.Callback
implements AutoCloseable {