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) { if (getTag() instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag(); WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
isBadged = !Process.myUserHandle().equals(info.user) isBadged = !Process.myUserHandle().equals(info.user)
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_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.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 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_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.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET; import static com.android.launcher3.LauncherState.NO_OFFSET;
@@ -283,7 +282,6 @@ public class QuickstepLauncher extends Launcher {
if (mAllAppsPredictions != null if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION && (info.itemType == ITEM_TYPE_APPLICATION
|| info.itemType == ITEM_TYPE_SHORTCUT
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size(); int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@@ -1162,7 +1160,6 @@ public class QuickstepLauncher extends Launcher {
} }
switch (info.itemType) { switch (info.itemType) {
case Favorites.ITEM_TYPE_APPLICATION: case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPWIDGET: case Favorites.ITEM_TYPE_APPWIDGET:
// Fall through and continue if it's an app, shortcut, or widget // 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.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_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.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat; 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.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; 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.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState; import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper; import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -53,8 +52,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -76,17 +73,9 @@ public final class WidgetsPredicationUpdateTaskTest {
private LauncherModelHelper mModelHelper; private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle; private UserHandle mUserHandle;
@Mock
private IconCache mIconCache;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
mModelHelper = new LauncherModelHelper(); mModelHelper = new LauncherModelHelper();
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mUserHandle = myUserHandle(); mUserHandle = myUserHandle();
mApp1Provider1 = createAppWidgetProviderInfo( mApp1Provider1 = createAppWidgetProviderInfo(
@@ -114,16 +103,12 @@ public final class WidgetsPredicationUpdateTaskTest {
.collect(Collectors.toList()); .collect(Collectors.toList());
}).when(manager).getInstalledProvidersForPackage(any(), eq(myUserHandle())); }).when(manager).getInstalledProvidersForPackage(any(), eq(myUserHandle()));
// 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace. LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
mModelHelper.initializeData("widgets_predication_update_task_data"); .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(); MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get();
MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update( mModelHelper.loadModelSync();
LauncherAppState.getInstance(mModelHelper.sandboxContext),
/* packageUser= */ null));
MODEL_EXECUTOR.submit(() -> { }).get();
MAIN_EXECUTOR.submit(() -> { }).get();
} }
@After @After
@@ -132,65 +117,72 @@ public final class WidgetsPredicationUpdateTaskTest {
} }
@Test @Test
public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() {
throws Exception { // Run on model executor so that no other task runs in the middle.
// WHEN newPredicationTask is executed with app predication of 5 apps. runOnExecutorSync(MODEL_EXECUTOR, () -> {
AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1", // WHEN newPredicationTask is executed with app predication of 5 apps.
mUserHandle); AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1", mUserHandle);
mUserHandle); AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className", mUserHandle);
mUserHandle); AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1", mUserHandle);
mUserHandle); AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1", mUserHandle);
mUserHandle); AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mModelHelper.executeTaskForTest( mUserHandle);
newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1))) mCallback.mRecommendedWidgets = null;
.forEach(Runnable::run); mModelHelper.getModel().enqueueModelUpdateTask(
newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)));
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
// THEN only 2 widgets are returned because // THEN only 2 widgets are returned because
// 1. app5/provider1 & app4/provider1 have already been added to workspace. They are // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
// excluded from the result. // excluded from the result.
// 2. app3 doesn't have a widget. // 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. // 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
.stream() .stream()
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo) .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2); assertThat(recommendedWidgets).hasSize(2);
assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1); assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1); assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
});
} }
@Test @Test
public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty() public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty() {
throws Exception { runOnExecutorSync(MODEL_EXECUTOR, () -> {
// Not installed widget // Not installed widget
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3", AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
mUserHandle); mUserHandle);
// Not installed app // Not installed app
AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1", AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
mUserHandle); mUserHandle);
// Workspace added widgets // Workspace added widgets
AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1", AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
mUserHandle); mUserHandle);
AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1", AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
mUserHandle); mUserHandle);
mModelHelper.executeTaskForTest(
newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)))
.forEach(Runnable::run);
// THEN only 2 widgets are returned because the launcher only filters out non-exist widgets. mCallback.mRecommendedWidgets = null;
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items mModelHelper.getModel().enqueueModelUpdateTask(
.stream() newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)));
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo) runOnExecutorSync(MAIN_EXECUTOR, () -> { });
.collect(Collectors.toList());
assertThat(recommendedWidgets).hasSize(2); // THEN only 2 widgets are returned because the launcher only filters out
// Another widget from the same package // non-exist widgets.
assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2); List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1); .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( private void assertWidgetInfo(
+1 -2
View File
@@ -1928,7 +1928,7 @@ public class Launcher extends StatefulActivity<LauncherState>
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
addAppWidgetFromDrop((PendingAddWidgetInfo) info); addAppWidgetFromDrop((PendingAddWidgetInfo) info);
break; break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
processShortcutFromDrop((PendingAddShortcutInfo) info); processShortcutFromDrop((PendingAddShortcutInfo) info);
break; break;
default: default:
@@ -2435,7 +2435,6 @@ public class Launcher extends StatefulActivity<LauncherState>
final View view; final View view;
switch (item.itemType) { switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
WorkspaceItemInfo info = (WorkspaceItemInfo) item; WorkspaceItemInfo info = (WorkspaceItemInfo) item;
view = createShortcut(info); view = createShortcut(info);
@@ -104,6 +104,8 @@ public class LauncherAppState implements SafeCloseable {
}); });
mContext.getSystemService(LauncherApps.class).registerCallback(mModel); mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
mOnTerminateCallback.add(() ->
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel));
SimpleBroadcastReceiver modelChangeReceiver = SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent); new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
@@ -123,8 +125,9 @@ public class LauncherAppState implements SafeCloseable {
mOnTerminateCallback.add(userChangeListener::close); mOnTerminateCallback.add(userChangeListener::close);
LockedUserState.get(context).runOnUserUnlocked(() -> { LockedUserState.get(context).runOnUserUnlocked(() -> {
CustomWidgetManager.INSTANCE.get(mContext) CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts); cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
IconObserver observer = new IconObserver(); IconObserver observer = new IconObserver();
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener( SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
@@ -159,6 +162,7 @@ public class LauncherAppState implements SafeCloseable {
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext), mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null); iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close); mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
} }
private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -180,9 +184,6 @@ public class LauncherAppState implements SafeCloseable {
*/ */
@Override @Override
public void close() { public void close() {
mModel.destroy();
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
mOnTerminateCallback.executeAllAndDestroy(); mOnTerminateCallback.executeAllAndDestroy();
} }
@@ -89,7 +89,9 @@ public class LauncherSettings {
/** /**
* The gesture is an application created shortcut * 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; public static final int ITEM_TYPE_SHORTCUT = 1;
/** /**
@@ -213,7 +215,6 @@ public class LauncherSettings {
public static final String itemTypeToString(int type) { public static final String itemTypeToString(int type) {
switch(type) { switch(type) {
case ITEM_TYPE_APPLICATION: return "APP"; case ITEM_TYPE_APPLICATION: return "APP";
case ITEM_TYPE_SHORTCUT: return "SHORTCUT";
case ITEM_TYPE_FOLDER: return "FOLDER"; case ITEM_TYPE_FOLDER: return "FOLDER";
case ITEM_TYPE_APPWIDGET: return "WIDGET"; case ITEM_TYPE_APPWIDGET: return "WIDGET";
case ITEM_TYPE_CUSTOM_APPWIDGET: return "CUSTOMWIDGET"; 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); != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut = boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION || (info.itemType == ITEM_TYPE_APPLICATION ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT); info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
return (aboveShortcut && willBecomeShortcut); return (aboveShortcut && willBecomeShortcut);
@@ -2759,7 +2758,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info; final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
boolean findNearestVacantCell = true; 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, mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
cellLayout, mTargetCell); cellLayout, mTargetCell);
float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter( float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
@@ -2832,8 +2831,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
View view; View view;
switch (info.itemType) { switch (info.itemType) {
case ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION: case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
if (info instanceof WorkspaceItemFactory) { if (info instanceof WorkspaceItemFactory) {
@@ -881,7 +881,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
final ItemInfo item = d.dragInfo; final ItemInfo item = d.dragInfo;
final int itemType = item.itemType; final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_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) { private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType; final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) && itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
item != mInfo && !mFolder.isOpen()); item != mInfo && !mFolder.isOpen());
} }
@@ -34,13 +34,9 @@ import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo; import android.appwidget.AppWidgetProviderInfo;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -78,8 +74,6 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon; 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.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -183,7 +177,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
private final DeviceProfile mDp; private final DeviceProfile mDp;
private final DeviceProfile mDpOrig; private final DeviceProfile mDpOrig;
private final Rect mInsets; private final Rect mInsets;
private final WorkspaceItemInfo mWorkspaceItemInfo;
private final LayoutInflater mHomeElementInflater; private final LayoutInflater mHomeElementInflater;
private final InsettableFrameLayout mRootView; private final InsettableFrameLayout mRootView;
private final Hotseat mHotseat; private final Hotseat mHotseat;
@@ -221,19 +214,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom()); mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom());
mDp.updateInsets(mInsets); 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( mHomeElementInflater = LayoutInflater.from(
new ContextThemeWrapper(this, R.style.HomeScreenElementTheme)); new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
mHomeElementInflater.setFactory2(this); mHomeElementInflater.setFactory2(this);
@@ -483,7 +463,6 @@ public class LauncherPreviewRenderer extends ContextWrapper
for (ItemInfo itemInfo : currentWorkspaceItems) { for (ItemInfo itemInfo : currentWorkspaceItems) {
switch (itemInfo.itemType) { switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPLICATION: case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
inflateAndAddIcon((WorkspaceItemInfo) itemInfo); inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
break; break;
@@ -91,8 +91,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
List<ItemInfo> filteredItems = new ArrayList<>(); List<ItemInfo> filteredItems = new ArrayList<>();
for (Pair<ItemInfo, Object> entry : mItemList) { for (Pair<ItemInfo, Object> entry : mItemList) {
ItemInfo item = entry.first; ItemInfo item = entry.first;
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
// Short-circuit this logic if the icon exists somewhere on the workspace // Short-circuit this logic if the icon exists somewhere on the workspace
if (shortcutExists(dataModel, item.getIntent(), item.user)) { if (shortcutExists(dataModel, item.getIntent(), item.user)) {
continue; continue;
@@ -210,7 +210,6 @@ public class BgDataModel {
// Fall through. // Fall through.
} }
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
workspaceItems.remove(item); workspaceItems.remove(item);
break; break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -245,7 +244,6 @@ public class BgDataModel {
break; break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
workspaceItems.add(item); workspaceItems.add(item);
@@ -455,7 +455,6 @@ public class GridSizeMigrationUtil {
try { try {
// calculate weight // calculate weight
switch (entry.itemType) { switch (entry.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent); entry.mIntent = c.getString(indexIntent);
@@ -531,7 +530,6 @@ public class GridSizeMigrationUtil {
try { try {
// calculate weight // calculate weight
switch (entry.itemType) { switch (entry.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent); entry.mIntent = c.getString(indexIntent);
@@ -286,7 +286,6 @@ public class ItemInstallQueue {
final WorkspaceItemInfo si = new WorkspaceItemInfo(); final WorkspaceItemInfo si = new WorkspaceItemInfo();
si.user = user; si.user = user;
si.itemType = ITEM_TYPE_APPLICATION;
LauncherActivityInfo lai; LauncherActivityInfo lai;
boolean usePackageIcon = laiList.isEmpty(); boolean usePackageIcon = laiList.isEmpty();
@@ -193,9 +193,7 @@ public class LoaderCursor extends CursorWrapper {
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo( public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
WorkspaceItemInfo wai, boolean useLowResIcon) { WorkspaceItemInfo wai, boolean useLowResIcon) {
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_SHORTCUT byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
|| itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
|| restoreFlag != 0
? getIconBlob() : null; ? getIconBlob() : null;
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon); return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
@@ -347,7 +345,6 @@ public class LoaderCursor extends CursorWrapper {
} }
final WorkspaceItemInfo info = new WorkspaceItemInfo(); final WorkspaceItemInfo info = new WorkspaceItemInfo();
info.itemType = Favorites.ITEM_TYPE_APPLICATION;
info.user = user; info.user = user;
info.intent = newIntent; info.intent = newIntent;
@@ -503,7 +503,6 @@ public class LoaderTask implements Runnable {
boolean allowMissingTarget = false; boolean allowMissingTarget = false;
switch (c.itemType) { switch (c.itemType) {
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION: case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
Intent intent = c.parseIntent(); Intent intent = c.parseIntent();
@@ -517,9 +516,8 @@ public class LoaderTask implements Runnable {
ComponentName cn = intent.getComponent(); ComponentName cn = intent.getComponent();
String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
if (TextUtils.isEmpty(targetPkg) if (TextUtils.isEmpty(targetPkg)) {
&& c.itemType != Favorites.ITEM_TYPE_SHORTCUT) { c.markDeleted("Shortcuts can't have null package");
c.markDeleted("Only legacy shortcuts can have null package");
return; return;
} }
@@ -498,7 +498,6 @@ public class ModelWriter {
modelItem.container == Favorites.CONTAINER_HOTSEAT)) { modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
switch (modelItem.itemType) { switch (modelItem.itemType) {
case Favorites.ITEM_TYPE_APPLICATION: case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_FOLDER: case Favorites.ITEM_TYPE_FOLDER:
if (!mBgDataModel.workspaceItems.contains(modelItem)) { 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_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; 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_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.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID; import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
@@ -87,7 +86,6 @@ public class ItemInfo {
/** /**
* One of {@link Favorites#ITEM_TYPE_APPLICATION}, * One of {@link Favorites#ITEM_TYPE_APPLICATION},
* {@link Favorites#ITEM_TYPE_SHORTCUT},
* {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT} * {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
* {@link Favorites#ITEM_TYPE_FOLDER}, * {@link Favorites#ITEM_TYPE_FOLDER},
* {@link Favorites#ITEM_TYPE_APP_PAIR}, * {@link Favorites#ITEM_TYPE_APP_PAIR},
@@ -361,13 +359,6 @@ public class ItemInfo {
}) })
.orElse(LauncherAtom.Shortcut.newBuilder())); .orElse(LauncherAtom.Shortcut.newBuilder()));
break; 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: case ITEM_TYPE_APPWIDGET:
itemBuilder itemBuilder
.setWidget(nullableComponent .setWidget(nullableComponent
@@ -96,7 +96,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
public WorkspaceItemInfo() { public WorkspaceItemInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
} }
public WorkspaceItemInfo(WorkspaceItemInfo info) { public WorkspaceItemInfo(WorkspaceItemInfo info) {
@@ -205,8 +205,8 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
@Override @Override
public ComponentName getTargetComponent() { public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent(); ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag( if (cn == null && hasStatusFlag(
FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) { 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 // 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 // a packageName. In that case create a empty componentName instead of adding additional
// check everywhere. // check everywhere.
@@ -72,7 +72,7 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn
} }
public int getItemType() { public int getItemType() {
return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; return LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
} }
@Override @Override
@@ -250,6 +250,11 @@ public class IntArray implements Cloneable, Iterable<Integer> {
return b.toString(); return b.toString();
} }
@Override
public String toString() {
return "IntArray [" + toConcatString() + "]";
}
public static IntArray fromConcatString(String concatString) { public static IntArray fromConcatString(String concatString) {
StringTokenizer tokenizer = new StringTokenizer(concatString, ","); StringTokenizer tokenizer = new StringTokenizer(concatString, ",");
int[] array = new int[tokenizer.countTokens()]; int[] array = new int[tokenizer.countTokens()];
@@ -48,8 +48,8 @@ public class MainThreadInitializedObject<T> {
} }
public T get(Context context) { public T get(Context context) {
if (context instanceof SandboxContext) { if (context instanceof SandboxContext sc) {
return ((SandboxContext) context).getObject(this, mProvider); return sc.getObject(this);
} }
if (mValue == null) { 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 * Find a cached object from mObjectMap if we have already created one. If not, generate
* an object using the provider. * an object using the provider.
*/ */
protected <T> T getObject(MainThreadInitializedObject<T> object, protected <T> T getObject(MainThreadInitializedObject<T> object) {
ObjectProvider<T> provider) {
synchronized (mDestroyLock) { synchronized (mDestroyLock) {
if (mDestroyed) { if (mDestroyed) {
Log.e(TAG, "Static object access with a destroyed context"); Log.e(TAG, "Static object access with a destroyed context");
} }
T t = (T) mObjectMap.get(object); T t = (T) mObjectMap.get(object);
if (t != null) { if (t != null) {
return t; return t;
} }
if (Looper.myLooper() == Looper.getMainLooper()) { 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, // Check if we've explicitly allowed the object or if it's a SafeCloseable,
// it will get destroyed in onDestroy() // it will get destroyed in onDestroy()
if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) { if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
throw new IllegalStateException( throw new IllegalStateException("Leaking unknown objects "
"Leaking unknown objects " + object + " " + provider + " " + t); + object + " " + object.mProvider + " " + t);
} }
mObjectMap.put(object, t); mObjectMap.put(object, t);
mOrderedObjects.add(t); mOrderedObjects.add(t);
@@ -157,15 +155,15 @@ public class MainThreadInitializedObject<T> {
} }
try { try {
return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get(); return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@UiThread @UiThread
protected <T> T createObject(ObjectProvider<T> provider) { protected <T> T createObject(MainThreadInitializedObject<T> object) {
return provider.get(this); return object.mProvider.get(this);
} }
} }
} }
@@ -332,6 +332,12 @@ public interface ActivityContext {
return null; 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) ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
: makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON : makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */); ? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
@@ -343,13 +349,11 @@ public interface ActivityContext {
intent.setSourceBounds(Utilities.getViewBounds(v)); intent.setSourceBounds(Utilities.getViewBounds(v));
} }
try { 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) { if (isShortcut) {
// Shortcuts need some special checks due to legacy reasons. String id = ((WorkspaceItemInfo) item).getDeepShortcutId();
startShortcutIntentSafely(intent, optsBundle, item); String packageName = intent.getPackage();
((Context) this).getSystemService(LauncherApps.class).startShortcut(
packageName, id, intent.getSourceBounds(), optsBundle, user);
} else if (user == null || user.equals(Process.myUserHandle())) { } else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity // Could be launching some bookkeeping activity
context.startActivity(intent, optsBundle); context.startActivity(intent, optsBundle);
@@ -424,55 +428,6 @@ public interface ActivityContext {
return new ActivityOptionsWrapper(options, new RunnableList()); 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() { default CellPosMapper getCellPosMapper() {
return CellPosMapper.DEFAULT; return CellPosMapper.DEFAULT;
} }
@@ -290,7 +290,6 @@ public class OptionsPopupView extends ArrowPopup<Launcher>
static WorkspaceItemInfo placeholderInfo(Intent intent) { static WorkspaceItemInfo placeholderInfo(Intent intent) {
WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo(); WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
placeholderInfo.intent = intent; placeholderInfo.intent = intent;
placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS; placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
return placeholderInfo; 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.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState 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.model.data.WorkspaceItemInfo
import com.android.launcher3.util.ContentWriter
import com.android.launcher3.util.GridOccupancy import com.android.launcher3.util.GridOccupancy
import com.android.launcher3.util.IntArray import com.android.launcher3.util.IntArray
import com.android.launcher3.util.IntSparseArrayMap import com.android.launcher3.util.IntSparseArrayMap
import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper 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 import java.util.UUID
/** Base class for workspace related tests. */ /** Base class for workspace related tests. */
@@ -38,6 +39,7 @@ abstract class AbstractWorkspaceModelTest {
val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4)) val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
} }
protected lateinit var mLayoutBuilder: LauncherLayoutBuilder
protected lateinit var mTargetContext: Context protected lateinit var mTargetContext: Context
protected lateinit var mIdp: InvariantDeviceProfile protected lateinit var mIdp: InvariantDeviceProfile
protected lateinit var mAppState: LauncherAppState protected lateinit var mAppState: LauncherAppState
@@ -47,6 +49,7 @@ abstract class AbstractWorkspaceModelTest {
protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy> protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
open fun setup() { open fun setup() {
mLayoutBuilder = LauncherLayoutBuilder()
mModelHelper = LauncherModelHelper() mModelHelper = LauncherModelHelper()
mTargetContext = mModelHelper.sandboxContext mTargetContext = mModelHelper.sandboxContext
mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext] 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. */ /** Sets up workspaces with the given screen IDs with some items and a 2x2 space. */
fun setupWorkspaces(screenIdsWithItems: List<Int>) { fun setupWorkspaces(screenIdsWithItems: List<Int>) {
var nextItemId = 1 screenIdsWithItems.forEach { screenId -> setupWorkspace(screenId, nonEmptyScreenSpaces) }
screenIdsWithItems.forEach { screenId -> mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces) mIdp.numRows = 5
} mIdp.numColumns = mIdp.numRows
mModelHelper.loadModelSync()
} }
/** /**
@@ -78,30 +82,23 @@ abstract class AbstractWorkspaceModelTest {
screen1: List<Rect>? = null, screen1: List<Rect>? = null,
screen2: List<Rect>? = null, screen2: List<Rect>? = null,
screen3: 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>?>) { private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
var nextItemId = 1
workspaceSpaces.forEachIndexed { screenId, spaces -> workspaceSpaces.forEachIndexed { screenId, spaces ->
if (spaces != null) { if (spaces != null) {
nextItemId = setupWorkspace(nextItemId, screenId, spaces) setupWorkspace(screenId, spaces)
} }
} }
} }
private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int { private fun setupWorkspace(screenId: Int, spaces: List<Rect>) {
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
val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows) val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true) occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
spaces.forEach { spaceRect -> occupancy.markCells(spaceRect, false) } spaces.forEach { spaceRect -> occupancy.markCells(spaceRect, false) }
@@ -109,35 +106,22 @@ abstract class AbstractWorkspaceModelTest {
mScreenOccupancy.append(screenId, occupancy) mScreenOccupancy.append(screenId, occupancy)
for (x in 0 until mIdp.numColumns) { for (x in 0 until mIdp.numColumns) {
for (y in 0 until mIdp.numRows) { for (y in 0 until mIdp.numRows) {
if (!occupancy.cells[x][y]) { if (occupancy.cells[x][y]) {
continue 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() = fun getExistingItem() =
WorkspaceItemInfo().apply { intent = Intent().setComponent(ComponentName("a", "b")) } WorkspaceItemInfo().apply {
intent = AppInfo.makeLaunchIntent(ComponentName(TEST_PACKAGE, TEST_ACTIVITY))
}
fun getNewItem(): WorkspaceItemInfo { fun getNewItem(): WorkspaceItemInfo {
val itemPackage = UUID.randomUUID().toString() val itemPackage = UUID.randomUUID().toString()
return WorkspaceItemInfo().apply { 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.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors import com.android.launcher3.util.Executors
import com.android.launcher3.util.IntArray 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.any
import com.android.launcher3.util.eq import com.android.launcher3.util.eq
import com.android.launcher3.util.same import com.android.launcher3.util.same
@@ -32,8 +32,6 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
@@ -46,11 +44,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
@Captor private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>> private lateinit var mDataModelCallbacks: MyCallbacks
@Captor private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
@Mock private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
@Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
@@ -58,7 +52,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
override fun setup() { override fun setup() {
super.setup() super.setup()
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(mDataModelCallbacks.getPagesToBindSynchronously(any())).thenReturn(IntSet()) mDataModelCallbacks = MyCallbacks()
Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
.get() .get()
} }
@@ -105,7 +99,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
assertThat(addedItems.size).isEqualTo(0) assertThat(addedItems.size).isEqualTo(0)
verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks) verifyZeroInteractions(mWorkspaceItemSpaceFinder)
} }
@Test @Test
@@ -191,22 +185,14 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
): List<AddedItem> { ): List<AddedItem> {
setupWorkspaces(nonEmptyScreenIds) setupWorkspaces(nonEmptyScreenIds)
val task = newTask(*itemsToAdd) val task = newTask(*itemsToAdd)
var updateCount = 0
mModelHelper.executeTaskForTest(task).forEach {
updateCount++
it.run()
}
val addedItems = mutableListOf<AddedItem>() val addedItems = mutableListOf<AddedItem>()
if (updateCount > 0) {
verify(mDataModelCallbacks) runOnExecutorSync(Executors.MODEL_EXECUTOR) {
.bindAppsAdded( mDataModelCallbacks.addedItems.clear()
any(), mModelHelper.model.enqueueModelUpdateTask(task)
mNotAnimatedItemArgumentCaptor.capture(), runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
mAnimatedItemArgumentCaptor.capture() addedItems.addAll(mDataModelCallbacks.addedItems)
)
addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
} }
return addedItems return addedItems
@@ -224,3 +210,17 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
} }
private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) 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; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.Context; 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.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.data.FolderInfo;
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.WorkspaceItemInfo; 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.LauncherModelHelper;
import com.android.launcher3.util.PackageUserKey;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -35,6 +34,7 @@ import org.junit.runner.RunWith;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
/** /**
* Tests for {@link CacheDataUpdatedTask} * Tests for {@link CacheDataUpdatedTask}
@@ -43,49 +43,40 @@ import java.util.HashSet;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest { 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 LauncherModelHelper mModelHelper;
private Context mContext;
private int mSession1;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
mModelHelper = new LauncherModelHelper(); 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 LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
Context context = mModelHelper.sandboxContext; .atHotseat(1).putFolder("MyFolder")
IconCache iconCache = LauncherAppState.getInstance(context).getIconCache(); .addApp(TEST_PACKAGE, TEST_ACTIVITY) // 2
CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() { .addApp(TEST_PACKAGE, TEST_ACTIVITY2) // 3
@Override .addApp(TEST_PACKAGE, TEST_ACTIVITY3) // 4
@NonNull
public ComponentName getComponent(@NonNull ItemInfo info) {
return info.getTargetComponent();
}
@NonNull // Pending App 1
@Override .addApp(PENDING_APP_1, TEST_ACTIVITY) // 5
public UserHandle getUser(@NonNull ItemInfo info) { .addApp(PENDING_APP_1, TEST_ACTIVITY2) // 6
return info.user; .addApp(PENDING_APP_1, TEST_ACTIVITY3) // 7
}
@NonNull // Pending App 2
@Override .addApp(PENDING_APP_2, TEST_ACTIVITY) // 8
public CharSequence getLabel(@NonNull ItemInfo info) { .addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
return NEW_LABEL_PREFIX + info.id; .addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10
} .build();
mModelHelper.setupDefaultLayoutProvider(builder);
@NonNull mModelHelper.loadModelSync();
@Override assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
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);
}
} }
@After @After
@@ -94,27 +85,63 @@ public class CacheDataUpdatedTaskTest {
} }
private CacheDataUpdatedTask newTask(int op, String... pkg) { private CacheDataUpdatedTask newTask(int op, String... pkg) {
return new CacheDataUpdatedTask(op, Process.myUserHandle(), return new CacheDataUpdatedTask(op, myUserHandle(),
new HashSet<>(Arrays.asList(pkg))); new HashSet<>(Arrays.asList(pkg)));
} }
@Test @Test
public void testCacheUpdate_update_apps() throws Exception { public void testCacheUpdate_update_apps() {
// Clear all icons from apps list so that its easy to check what was updated // Run on model executor so that no other task runs in the middle.
for (AppInfo info : mModelHelper.getAllAppsList().data) { runOnExecutorSync(MODEL_EXECUTOR, () -> {
info.bitmap = BitmapInfo.LOW_RES_INFO; // 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) // Verify that only the app icons of TEST_PACKAGE (id 2, 3, 4) are updated.
// is not updated verifyUpdate(2, 3, 4);
verifyUpdate(1, 2); });
}
// Verify that only app1 var updated in allAppsList @Test
assertFalse(mModelHelper.getAllAppsList().data.isEmpty()); public void testSessionUpdate_ignores_normal_apps() {
for (AppInfo info : mModelHelper.getAllAppsList().data) { // Run on model executor so that no other task runs in the middle.
if (info.componentName.getPackageName().equals("app1")) { 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()); assertFalse(info.bitmap.isNullOrLowRes());
} else { } else {
assertTrue(info.bitmap.isNullOrLowRes()); assertTrue(info.bitmap.isNullOrLowRes());
@@ -122,33 +149,7 @@ public class CacheDataUpdatedTaskTest {
} }
} }
@Test private List<WorkspaceItemInfo> allItems() {
public void testSessionUpdate_ignores_normal_apps() throws Exception { return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
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());
}
}
} }
} }
@@ -24,7 +24,6 @@ import android.graphics.Point
import android.os.Process import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
@@ -108,8 +107,8 @@ class GridSizeMigrationUtilTest {
fun testMigration() { fun testMigration() {
// Src Hotseat icons // Src Hotseat icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) 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_DEEP_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, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE) addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Src grid icons // Src grid icons
// _ _ _ _ _ // _ _ _ _ _
@@ -124,7 +123,7 @@ class GridSizeMigrationUtilTest {
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE) addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
// Dest hotseat icons // 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 // Dest grid icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10) addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
@@ -219,8 +218,8 @@ class GridSizeMigrationUtilTest {
// Hotseat items in grid A // Hotseat items in grid A
// 1 2 _ 3 4 // 1 2 _ 3 4
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) 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_DEEP_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, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE) addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Workspace items in grid A // Workspace items in grid A
// _ _ _ _ _ // _ _ _ _ _
@@ -235,7 +234,7 @@ class GridSizeMigrationUtilTest {
// Hotseat items in grid B // Hotseat items in grid B
// 2 _ _ _ // 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 // Workspace items in grid B
// _ _ _ _ // _ _ _ _
// _ _ _ 10 // _ _ _ 10
@@ -291,7 +290,7 @@ class GridSizeMigrationUtilTest {
null null
) )
?: throw IllegalStateException() ?: throw IllegalStateException()
var locMap = parseLocMap(context, c) var locMap = parseLocMap(c)
// Expected items in grid B // Expected items in grid B
// _ _ _ _ // _ _ _ _
// 5 6 7 8 // 5 6 7 8
@@ -348,7 +347,7 @@ class GridSizeMigrationUtilTest {
null null
) )
?: throw IllegalStateException() ?: throw IllegalStateException()
locMap = parseLocMap(context, c) locMap = parseLocMap(c)
// Expected workspace items in grid A // Expected workspace items in grid A
// _ _ _ _ _ // _ _ _ _ _
// _ _ _ _ 5 // _ _ _ _ 5
@@ -410,7 +409,7 @@ class GridSizeMigrationUtilTest {
null null
) )
?: throw IllegalStateException() ?: throw IllegalStateException()
locMap = parseLocMap(context, c) locMap = parseLocMap(c)
// Expected workspace items in grid B // Expected workspace items in grid B
// _ _ _ _ // _ _ _ _
// 5 6 _ 8 // 5 6 _ 8
@@ -436,7 +435,7 @@ class GridSizeMigrationUtilTest {
c.close() 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 // Check workspace items
val intentIndex = c.getColumnIndex(INTENT) val intentIndex = c.getColumnIndex(INTENT)
val screenIndex = c.getColumnIndex(SCREEN) val screenIndex = c.getColumnIndex(SCREEN)
@@ -465,7 +464,16 @@ class GridSizeMigrationUtilTest {
1, 1,
TMP_TABLE 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( addItem(
ITEM_TYPE_APPLICATION, ITEM_TYPE_APPLICATION,
2, 2,
@@ -476,7 +484,16 @@ class GridSizeMigrationUtilTest {
3, 3,
TMP_TABLE 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 val numSrcDatabaseHotseatIcons = srcHotseatItems.size
idp.numDatabaseHotseatIcons = 6 idp.numDatabaseHotseatIcons = 6
@@ -532,9 +549,9 @@ class GridSizeMigrationUtilTest {
@Test @Test
fun migrateFromLargerHotseat() { fun migrateFromLargerHotseat() {
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE) 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_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) addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
idp.numDatabaseHotseatIcons = 4 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.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; 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_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.OPTIONS;
import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID; import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
import static com.android.launcher3.LauncherSettings.Favorites.RANK; import static com.android.launcher3.LauncherSettings.Favorites.RANK;
@@ -158,13 +158,13 @@ public class LoaderCursorTest {
@Test @Test
public void loadSimpleShortcut() { public void loadSimpleShortcut() {
initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut"); initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
assertTrue(mLoaderCursor.moveToNext()); assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem(); WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user)); assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user));
assertEquals("my-shortcut", info.title); assertEquals("my-shortcut", info.title);
assertEquals(ITEM_TYPE_SHORTCUT, info.itemType); assertEquals(ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
} }
@Test @Test
@@ -1,5 +1,12 @@
package com.android.launcher3.model; 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 static org.junit.Assert.assertEquals;
import androidx.test.ext.junit.runners.AndroidJUnit4; 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.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PackageInstallInfo; 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 com.android.launcher3.util.LauncherModelHelper;
import org.junit.After; import org.junit.After;
@@ -16,9 +25,6 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.HashSet;
/** /**
* Tests for {@link PackageInstallStateChangedTask} * Tests for {@link PackageInstallStateChangedTask}
*/ */
@@ -26,12 +32,36 @@ import java.util.HashSet;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest { 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 LauncherModelHelper mModelHelper;
private IntSet mDownloadingApps;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
mModelHelper = new LauncherModelHelper(); 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 @After
@@ -47,36 +77,45 @@ public class PackageInstallStateChangedTaskTest {
} }
@Test @Test
public void testSessionUpdate_ignore_installed() throws Exception { public void testSessionUpdate_ignore_installed() {
mModelHelper.executeTaskForTest(newTask("app1", 30)); // 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 // No shortcuts were updated
verifyProgressUpdate(0); verifyProgressUpdate(0);
});
} }
@Test @Test
public void testSessionUpdate_shortcuts_updated() throws Exception { public void testSessionUpdate_shortcuts_updated() {
mModelHelper.executeTaskForTest(newTask("app3", 30)); // 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 @Test
public void testSessionUpdate_widgets_updated() throws Exception { public void testSessionUpdate_widgets_updated() {
mModelHelper.executeTaskForTest(newTask("app4", 30)); // 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) { private void verifyProgressUpdate(int progress, int... idsUpdated) {
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated)); IntSet updates = IntSet.wrap(idsUpdated);
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (info instanceof WorkspaceItemInfo) { int expectedProgress = updates.contains(info.id) ? progress
assertEquals(updates.contains(info.id) ? progress: 100, : (mDownloadingApps.contains(info.id) ? 0 : 100);
((WorkspaceItemInfo) info).getProgressLevel()); if (info instanceof WorkspaceItemInfo wi) {
assertEquals(expectedProgress, wi.getProgressLevel());
} else { } else {
assertEquals(updates.contains(info.id) ? progress: -1, assertEquals(expectedProgress, ((LauncherAppWidgetInfo) info).installProgress);
((LauncherAppWidgetInfo) info).installProgress);
} }
} }
} }
@@ -87,9 +87,8 @@ class DisplayControllerTest {
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE), any())) whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
.thenReturn(windowManagerProxy) whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE), any())).thenReturn(launcherPrefs)
// Mock WindowManagerProxy // Mock WindowManagerProxy
val displayInfo = val displayInfo =
@@ -15,34 +15,31 @@
*/ */
package com.android.launcher3.util; 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 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.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.pm.PackageInstaller;
import android.content.Context; import android.content.pm.PackageInstaller.SessionParams;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo; import android.content.pm.ProviderInfo;
import android.content.res.Resources; import android.graphics.Bitmap;
import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.Process;
import android.provider.Settings; import android.provider.Settings;
import android.test.mock.MockContentResolver; import android.test.mock.MockContentResolver;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -57,13 +54,10 @@ import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue; 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.InstallSessionHelper;
import com.android.launcher3.pm.UserCache; import com.android.launcher3.pm.UserCache;
import com.android.launcher3.testing.TestInformationProvider; 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.util.window.WindowManagerProxy;
import com.android.launcher3.widget.custom.CustomWidgetManager; import com.android.launcher3.widget.custom.CustomWidgetManager;
import org.mockito.ArgumentCaptor;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStreamReader; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.function.Function;
/** /**
* Utility class to help manage Launcher Model and related objects for test. * Utility class to help manage Launcher Model and related objects for test.
*/ */
public class LauncherModelHelper { public class LauncherModelHelper {
public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP; public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
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_ACTIVITY = "com.android.launcher3.tests.Activity2"; 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. // Authority for providing a test default-workspace-layout data.
private static final String TEST_PROVIDER_AUTHORITY = 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_BITMAP_SIZE = 10;
private static final int DEFAULT_GRID_SIZE = 4; 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 SandboxModelContext sandboxContext;
public final long defaultProfileId; private final RunnableList mDestroyTask = new RunnableList();
private BgDataModel mDataModel; private BgDataModel mDataModel;
private AllAppsList mAllAppsList;
public LauncherModelHelper() { 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(); sandboxContext = new SandboxModelContext();
defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
.getSerialNumberForUser(Process.myUserHandle());
setupProvider(LauncherProvider.AUTHORITY, provider);
} }
public void setupProvider(String authority, ContentProvider provider) { public void setupProvider(String authority, ContentProvider provider) {
ProviderInfo providerInfo = new ProviderInfo(); sandboxContext.setupProvider(authority, provider);
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() { public LauncherModel getModel() {
@@ -151,16 +112,35 @@ public class LauncherModelHelper {
public synchronized BgDataModel getBgDataModel() { public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) { 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; return mDataModel;
} }
public synchronized AllAppsList getAllAppsList() { /**
if (mAllAppsList == null) { * Creates a installer session for the provided package.
mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList"); */
} public int createInstallerSession(String pkg) throws IOException {
return mAllAppsList; 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() { public void destroy() {
@@ -175,6 +155,8 @@ public class LauncherModelHelper {
waitOrThrow(l1); waitOrThrow(l1);
sandboxContext.onDestroy(); sandboxContext.onDestroy();
l2.countDown(); l2.countDown();
mDestroyTask.executeAllAndDestroy();
} }
private void waitOrThrow(CountDownLatch latch) { 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 * 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); setupProvider(TEST_PROVIDER_AUTHORITY, cp);
mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"settings delete secure launcher3.layout.provider")));
return this; return this;
} }
@@ -402,46 +209,16 @@ public class LauncherModelHelper {
*/ */
public void loadModelSync() throws ExecutionException, InterruptedException { public void loadModelSync() throws ExecutionException, InterruptedException {
Callbacks mockCb = new Callbacks() { }; 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.MODEL_EXECUTOR.submit(() -> { }).get();
Executors.MAIN_EXECUTOR.submit(() -> { }).get(); MAIN_EXECUTOR.submit(() -> { }).get();
Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get(); MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
} }
/** public static class SandboxModelContext extends SandboxContext {
* 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 {
private final MockContentResolver mMockResolver = new MockContentResolver();
private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>(); private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
private final PackageManager mPm; private final PackageManager mPm;
private final File mDbDir; private final File mDbDir;
@@ -453,8 +230,31 @@ public class LauncherModelHelper {
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE, DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE, SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
ItemInstallQueue.INSTANCE, WindowManagerProxy.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()); mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString()); 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) { public SandboxModelContext allow(MainThreadInitializedObject object) {
@@ -505,9 +305,30 @@ public class LauncherModelHelper {
mSpiedServices.put(name, result); mSpiedServices.put(name, result);
return result; return result;
} }
}
private static Context testContext() { public void setupProvider(String authority, ContentProvider provider) {
return getInstrumentation().getContext(); 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.io.OutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.ToIntFunction; import java.util.function.ToIntFunction;
@@ -154,6 +155,30 @@ public class TestUtil {
Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null); 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 private static class PackageInstallCheck extends LauncherApps.Callback
implements AutoCloseable { implements AutoCloseable {