Loading quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.os.UserHandle; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.BgDataModel.FixedContainerItems; Loading @@ -44,6 +45,7 @@ import com.android.launcher3.model.data.AppInfo; 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.shadows.ShadowDeviceFlag; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.ItemInfoMatcher; Loading @@ -60,6 +62,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowAppWidgetManager; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; Loading Loading @@ -174,6 +177,41 @@ public final class WidgetsPredicationUpdateTaskTest { 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( LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) { assertThat(actual.provider).isEqualTo(expected.provider); Loading quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java +37 −18 Original line number Diff line number Diff line Loading @@ -16,16 +16,17 @@ package com.android.launcher3.model; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.text.TextUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.BgDataModel.FixedContainerItems; 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.PackageUserKey; import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; Loading Loading @@ -56,9 +57,13 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask { Map<PackageUserKey, List<WidgetItem>> allWidgets = dataModel.widgetsModel.getAllWidgetsWithoutShortcuts(); ArrayList<ItemInfo> recommendedWidgetsInDescendingOrder = new ArrayList<>(); FixedContainerItems fixedContainerItems = mPredictorState.items; fixedContainerItems.items.clear(); if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) { for (AppTarget app : mTargets) { PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser()); PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser()); if (allWidgets.containsKey(packageUserKey)) { List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream() .filter(item -> Loading @@ -67,14 +72,28 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask { .collect(Collectors.toList()); if (notAddedWidgets.size() > 0) { // Even an apps have more than one widgets, we only include one widget. recommendedWidgetsInDescendingOrder.add( fixedContainerItems.items.add( new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo)); } } } FixedContainerItems fixedContainerItems = mPredictorState.items; fixedContainerItems.items.clear(); fixedContainerItems.items.addAll(recommendedWidgetsInDescendingOrder); } 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)); } } } bindExtraContainerItems(fixedContainerItems); // Don't store widgets prediction to disk because it is not used frequently. Loading quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java +6 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,12 @@ public class DeviceFlag extends DebugFlag { 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) { DeviceConfig.addOnPropertiesChangedListener( NAMESPACE_LAUNCHER, Loading robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java +19 −0 Original line number Diff line number Diff line Loading @@ -18,11 +18,15 @@ package com.android.launcher3.shadows; import android.content.Context; import androidx.annotation.Nullable; import com.android.launcher3.uioverrides.DeviceFlag; import com.android.launcher3.util.LooperExecutor; import org.robolectric.annotation.Implementation; 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. Loading @@ -30,6 +34,9 @@ import org.robolectric.annotation.Implements; @Implements(value = DeviceFlag.class, isInAndroidSdk = false) public class ShadowDeviceFlag { @RealObject private DeviceFlag mRealObject; @Nullable private Boolean mValue; /** * Mock change listener as it uses internal system classes not available to robolectric */ Loading @@ -40,4 +47,16 @@ public class ShadowDeviceFlag { protected static boolean getDeviceValue(String key, boolean 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); } } src/com/android/launcher3/config/FeatureFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,10 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag( "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, "Sends a notification whenever launcher encounters an uncaught exception."); Loading Loading
quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import android.os.UserHandle; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.BgDataModel.FixedContainerItems; Loading @@ -44,6 +45,7 @@ import com.android.launcher3.model.data.AppInfo; 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.shadows.ShadowDeviceFlag; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.ItemInfoMatcher; Loading @@ -60,6 +62,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowAppWidgetManager; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; Loading Loading @@ -174,6 +177,41 @@ public final class WidgetsPredicationUpdateTaskTest { 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( LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) { assertThat(actual.provider).isEqualTo(expected.provider); Loading
quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java +37 −18 Original line number Diff line number Diff line Loading @@ -16,16 +16,17 @@ package com.android.launcher3.model; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.text.TextUtils; import com.android.launcher3.LauncherAppState; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.BgDataModel.FixedContainerItems; 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.PackageUserKey; import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; Loading Loading @@ -56,9 +57,13 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask { Map<PackageUserKey, List<WidgetItem>> allWidgets = dataModel.widgetsModel.getAllWidgetsWithoutShortcuts(); ArrayList<ItemInfo> recommendedWidgetsInDescendingOrder = new ArrayList<>(); FixedContainerItems fixedContainerItems = mPredictorState.items; fixedContainerItems.items.clear(); if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) { for (AppTarget app : mTargets) { PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser()); PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser()); if (allWidgets.containsKey(packageUserKey)) { List<WidgetItem> notAddedWidgets = allWidgets.get(packageUserKey).stream() .filter(item -> Loading @@ -67,14 +72,28 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask { .collect(Collectors.toList()); if (notAddedWidgets.size() > 0) { // Even an apps have more than one widgets, we only include one widget. recommendedWidgetsInDescendingOrder.add( fixedContainerItems.items.add( new PendingAddWidgetInfo(notAddedWidgets.get(0).widgetInfo)); } } } FixedContainerItems fixedContainerItems = mPredictorState.items; fixedContainerItems.items.clear(); fixedContainerItems.items.addAll(recommendedWidgetsInDescendingOrder); } 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)); } } } bindExtraContainerItems(fixedContainerItems); // Don't store widgets prediction to disk because it is not used frequently. Loading
quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java +6 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,12 @@ public class DeviceFlag extends DebugFlag { 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) { DeviceConfig.addOnPropertiesChangedListener( NAMESPACE_LAUNCHER, Loading
robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java +19 −0 Original line number Diff line number Diff line Loading @@ -18,11 +18,15 @@ package com.android.launcher3.shadows; import android.content.Context; import androidx.annotation.Nullable; import com.android.launcher3.uioverrides.DeviceFlag; import com.android.launcher3.util.LooperExecutor; import org.robolectric.annotation.Implementation; 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. Loading @@ -30,6 +34,9 @@ import org.robolectric.annotation.Implements; @Implements(value = DeviceFlag.class, isInAndroidSdk = false) public class ShadowDeviceFlag { @RealObject private DeviceFlag mRealObject; @Nullable private Boolean mValue; /** * Mock change listener as it uses internal system classes not available to robolectric */ Loading @@ -40,4 +47,16 @@ public class ShadowDeviceFlag { protected static boolean getDeviceValue(String key, boolean 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); } }
src/com/android/launcher3/config/FeatureFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,10 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag( "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, "Sends a notification whenever launcher encounters an uncaught exception."); Loading