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