Merge "Add a flag to enable custom local filter for recommended widgets" into sc-dev am: 3e6b93cedb
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14339827 Change-Id: I0f16510ec895111828d70ad86a88a43d97ae62f8
This commit is contained in:
+38
@@ -36,6 +36,7 @@ import android.os.UserHandle;
|
|||||||
|
|
||||||
import com.android.launcher3.InvariantDeviceProfile;
|
import com.android.launcher3.InvariantDeviceProfile;
|
||||||
import com.android.launcher3.LauncherAppState;
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
import com.android.launcher3.icons.ComponentWithLabel;
|
import com.android.launcher3.icons.ComponentWithLabel;
|
||||||
import com.android.launcher3.icons.IconCache;
|
import com.android.launcher3.icons.IconCache;
|
||||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||||
@@ -44,6 +45,7 @@ import com.android.launcher3.model.data.AppInfo;
|
|||||||
import com.android.launcher3.model.data.ItemInfo;
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||||
|
import com.android.launcher3.shadows.ShadowDeviceFlag;
|
||||||
import com.android.launcher3.util.ComponentKey;
|
import com.android.launcher3.util.ComponentKey;
|
||||||
import com.android.launcher3.util.IntArray;
|
import com.android.launcher3.util.IntArray;
|
||||||
import com.android.launcher3.util.ItemInfoMatcher;
|
import com.android.launcher3.util.ItemInfoMatcher;
|
||||||
@@ -60,6 +62,7 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.shadow.api.Shadow;
|
||||||
import org.robolectric.shadows.ShadowAppWidgetManager;
|
import org.robolectric.shadows.ShadowAppWidgetManager;
|
||||||
import org.robolectric.shadows.ShadowPackageManager;
|
import org.robolectric.shadows.ShadowPackageManager;
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
import org.robolectric.util.ReflectionHelpers;
|
||||||
@@ -174,6 +177,41 @@ public final class WidgetsPredicationUpdateTaskTest {
|
|||||||
assertWidgetInfo(recommendedWidgets.get(2).info, mApp1Provider1);
|
assertWidgetInfo(recommendedWidgets.get(2).info, mApp1Provider1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void widgetsRecommendationRan_localFilterDisabled_shouldReturnWidgetsInPredicationOrder()
|
||||||
|
throws Exception {
|
||||||
|
ShadowDeviceFlag shadowDeviceFlag = Shadow.extract(
|
||||||
|
FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER);
|
||||||
|
shadowDeviceFlag.setValue(false);
|
||||||
|
|
||||||
|
// WHEN newPredicationTask is executed with 5 predicated widgets.
|
||||||
|
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
|
||||||
|
mUserHandle);
|
||||||
|
AppTarget widget2 = new AppTarget(new AppTargetId("app1"), "app1", "provider2",
|
||||||
|
mUserHandle);
|
||||||
|
// Not installed app
|
||||||
|
AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
|
||||||
|
mUserHandle);
|
||||||
|
// Not installed widget
|
||||||
|
AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider3",
|
||||||
|
mUserHandle);
|
||||||
|
AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
|
||||||
|
mUserHandle);
|
||||||
|
mModelHelper.executeTaskForTest(
|
||||||
|
newWidgetsPredicationTask(List.of(widget5, widget3, widget2, widget4, widget1)))
|
||||||
|
.forEach(Runnable::run);
|
||||||
|
|
||||||
|
// THEN only 3 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(3);
|
||||||
|
assertWidgetInfo(recommendedWidgets.get(0).info, mApp5Provider1);
|
||||||
|
assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider2);
|
||||||
|
assertWidgetInfo(recommendedWidgets.get(2).info, mApp1Provider1);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertWidgetInfo(
|
private void assertWidgetInfo(
|
||||||
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
|
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
|
||||||
assertThat(actual.provider).isEqualTo(expected.provider);
|
assertThat(actual.provider).isEqualTo(expected.provider);
|
||||||
|
|||||||
@@ -16,16 +16,17 @@
|
|||||||
package com.android.launcher3.model;
|
package com.android.launcher3.model;
|
||||||
|
|
||||||
import android.app.prediction.AppTarget;
|
import android.app.prediction.AppTarget;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.android.launcher3.LauncherAppState;
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||||
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
||||||
import com.android.launcher3.model.data.ItemInfo;
|
|
||||||
import com.android.launcher3.util.ComponentKey;
|
import com.android.launcher3.util.ComponentKey;
|
||||||
import com.android.launcher3.util.PackageUserKey;
|
import com.android.launcher3.util.PackageUserKey;
|
||||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -56,25 +57,43 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
|
|||||||
Map<PackageUserKey, List<WidgetItem>> allWidgets =
|
Map<PackageUserKey, List<WidgetItem>> allWidgets =
|
||||||
dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
|
dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
|
||||||
|
|
||||||
ArrayList<ItemInfo> recommendedWidgetsInDescendingOrder = new ArrayList<>();
|
FixedContainerItems fixedContainerItems = mPredictorState.items;
|
||||||
for (AppTarget app : mTargets) {
|
fixedContainerItems.items.clear();
|
||||||
PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser());
|
|
||||||
if (allWidgets.containsKey(packageUserKey)) {
|
if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) {
|
||||||
List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream()
|
for (AppTarget app : mTargets) {
|
||||||
.filter(item ->
|
PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(),
|
||||||
!widgetsInWorkspace.contains(
|
app.getUser());
|
||||||
new ComponentKey(item.componentName, item.user)))
|
if (allWidgets.containsKey(packageUserKey)) {
|
||||||
.collect(Collectors.toList());
|
List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream()
|
||||||
if (notAddedWidgets.size() > 0) {
|
.filter(item ->
|
||||||
// Even an apps have more than one widgets, we only include one widget.
|
!widgetsInWorkspace.contains(
|
||||||
recommendedWidgetsInDescendingOrder.add(
|
new ComponentKey(item.componentName, item.user)))
|
||||||
new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo));
|
.collect(Collectors.toList());
|
||||||
|
if (notAddedWidgets.size() > 0) {
|
||||||
|
// Even an apps have more than one widgets, we only include one widget.
|
||||||
|
fixedContainerItems.items.add(
|
||||||
|
new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Map<ComponentKey, WidgetItem> widgetItems =
|
||||||
|
allWidgets.values().stream().flatMap(List::stream)
|
||||||
|
.collect(Collectors.toMap(widget -> (ComponentKey) widget,
|
||||||
|
widget -> widget));
|
||||||
|
for (AppTarget app : mTargets) {
|
||||||
|
if (TextUtils.isEmpty(app.getClassName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ComponentKey targetWidget = new ComponentKey(
|
||||||
|
new ComponentName(app.getPackageName(), app.getClassName()), app.getUser());
|
||||||
|
if (widgetItems.containsKey(targetWidget)) {
|
||||||
|
fixedContainerItems.items.add(
|
||||||
|
new PendingAddWidgetInfo(widgetItems.get(targetWidget).widgetInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FixedContainerItems fixedContainerItems = mPredictorState.items;
|
|
||||||
fixedContainerItems.items.clear();
|
|
||||||
fixedContainerItems.items.addAll(recommendedWidgetsInDescendingOrder);
|
|
||||||
bindExtraContainerItems(fixedContainerItems);
|
bindExtraContainerItems(fixedContainerItems);
|
||||||
|
|
||||||
// Don't store widgets prediction to disk because it is not used frequently.
|
// Don't store widgets prediction to disk because it is not used frequently.
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ public class DeviceFlag extends DebugFlag {
|
|||||||
mListeners.remove(r);
|
mListeners.remove(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean get() {
|
||||||
|
// Override this method in order to let Robolectric ShadowDeviceFlag to stub it.
|
||||||
|
return super.get();
|
||||||
|
}
|
||||||
|
|
||||||
private void registerDeviceConfigChangedListener(Context context) {
|
private void registerDeviceConfigChangedListener(Context context) {
|
||||||
DeviceConfig.addOnPropertiesChangedListener(
|
DeviceConfig.addOnPropertiesChangedListener(
|
||||||
NAMESPACE_LAUNCHER,
|
NAMESPACE_LAUNCHER,
|
||||||
|
|||||||
@@ -18,11 +18,15 @@ package com.android.launcher3.shadows;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.launcher3.uioverrides.DeviceFlag;
|
import com.android.launcher3.uioverrides.DeviceFlag;
|
||||||
import com.android.launcher3.util.LooperExecutor;
|
import com.android.launcher3.util.LooperExecutor;
|
||||||
|
|
||||||
import org.robolectric.annotation.Implementation;
|
import org.robolectric.annotation.Implementation;
|
||||||
import org.robolectric.annotation.Implements;
|
import org.robolectric.annotation.Implements;
|
||||||
|
import org.robolectric.annotation.RealObject;
|
||||||
|
import org.robolectric.shadow.api.Shadow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
|
* Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
|
||||||
@@ -30,6 +34,9 @@ import org.robolectric.annotation.Implements;
|
|||||||
@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
|
@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
|
||||||
public class ShadowDeviceFlag {
|
public class ShadowDeviceFlag {
|
||||||
|
|
||||||
|
@RealObject private DeviceFlag mRealObject;
|
||||||
|
@Nullable private Boolean mValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock change listener as it uses internal system classes not available to robolectric
|
* Mock change listener as it uses internal system classes not available to robolectric
|
||||||
*/
|
*/
|
||||||
@@ -40,4 +47,16 @@ public class ShadowDeviceFlag {
|
|||||||
protected static boolean getDeviceValue(String key, boolean defaultValue) {
|
protected static boolean getDeviceValue(String key, boolean defaultValue) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
public boolean get() {
|
||||||
|
if (mValue != null) {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
return Shadow.directlyOn(mRealObject, DeviceFlag.class, "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(boolean value) {
|
||||||
|
mValue = new Boolean(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,10 @@ public final class FeatureFlags {
|
|||||||
public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
|
public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
|
||||||
"ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
|
"ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
|
||||||
|
|
||||||
|
public static final BooleanFlag ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER = new DeviceFlag(
|
||||||
|
"ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER", true,
|
||||||
|
"Enables a local filter for recommended widgets.");
|
||||||
|
|
||||||
public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false,
|
public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false,
|
||||||
"Sends a notification whenever launcher encounters an uncaught exception.");
|
"Sends a notification whenever launcher encounters an uncaught exception.");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user