Merge "Update widgetsModel to return pickable vs all widgets separately." into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
e532fc86b4
@@ -322,13 +322,14 @@ public class WidgetPickerActivity extends BaseActivity {
|
||||
stringCache.loadStrings(this);
|
||||
|
||||
bindStringCache(stringCache);
|
||||
bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter());
|
||||
bindWidgets(mModel.getWidgetsByPackageItemForPicker(),
|
||||
mModel.getDefaultWidgetsFilter());
|
||||
// Open sheet once widgets are available, so that it doesn't interrupt the open
|
||||
// animation.
|
||||
openWidgetsSheet();
|
||||
if (mUiSurface != null) {
|
||||
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
|
||||
mUiSurface, mModel.getWidgetsByComponentKey());
|
||||
mUiSurface, mModel.getWidgetsByComponentKeyForPicker());
|
||||
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class WidgetsPredictionUpdateTask implements ModelUpdateTask {
|
||||
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
|
||||
// being in predictions.
|
||||
Map<ComponentKey, WidgetItem> allEligibleWidgets =
|
||||
dataModel.widgetsModel.getWidgetsByComponentKey()
|
||||
dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().widgetInfo != null
|
||||
|
||||
+34
-1
@@ -91,6 +91,7 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
private AppWidgetProviderInfo mApp4Provider1;
|
||||
private AppWidgetProviderInfo mApp4Provider2;
|
||||
private AppWidgetProviderInfo mApp5Provider1;
|
||||
private AppWidgetProviderInfo mApp6PinOnlyProvider1;
|
||||
private List<AppWidgetProviderInfo> allWidgets;
|
||||
|
||||
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
|
||||
@@ -117,8 +118,14 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
ComponentName.createRelative("app4", ".provider2"));
|
||||
mApp5Provider1 = createAppWidgetProviderInfo(
|
||||
ComponentName.createRelative("app5", "provider1"));
|
||||
mApp6PinOnlyProvider1 = createAppWidgetProviderInfo(
|
||||
ComponentName.createRelative("app6", "provider1"),
|
||||
/*hideFromPicker=*/ true
|
||||
);
|
||||
|
||||
|
||||
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
|
||||
mApp4Provider1, mApp4Provider2, mApp5Provider1);
|
||||
mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1);
|
||||
|
||||
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
|
||||
doAnswer(i -> {
|
||||
@@ -270,6 +277,32 @@ public final class WidgetsPredicationUpdateTaskTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
|
||||
// Not installed widget - hence eligible
|
||||
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
|
||||
mUserHandle);
|
||||
// Provider marked as hidden from picker - hence not eligible
|
||||
AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1",
|
||||
mUserHandle);
|
||||
|
||||
mCallback.mRecommendedWidgets = null;
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(
|
||||
newWidgetsPredicationTask(List.of(widget1, widget6)));
|
||||
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
|
||||
|
||||
// Only widget 1 (and no widget 6 as its meant to be hidden from picker).
|
||||
List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
|
||||
.stream()
|
||||
.map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(recommendedWidgets).hasSize(1);
|
||||
assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1");
|
||||
});
|
||||
}
|
||||
|
||||
private void assertWidgetInfo(
|
||||
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
|
||||
assertThat(actual.provider).isEqualTo(expected.provider);
|
||||
|
||||
@@ -167,7 +167,7 @@ public class BaseLauncherBinder {
|
||||
return;
|
||||
}
|
||||
Map<PackageItemInfo, List<WidgetItem>>
|
||||
widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
|
||||
widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker();
|
||||
List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
|
||||
.build(widgetsByPackageItem);
|
||||
Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
|
||||
|
||||
@@ -77,7 +77,7 @@ class ModelTaskController(
|
||||
}
|
||||
|
||||
fun bindUpdatedWidgets(dataModel: BgDataModel) {
|
||||
val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem
|
||||
val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItemForPicker
|
||||
val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
|
||||
|
||||
val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
|
||||
|
||||
@@ -70,6 +70,7 @@ public class WidgetsModel {
|
||||
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
|
||||
@Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
|
||||
@Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
|
||||
@Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
|
||||
|
||||
/**
|
||||
* Returns all widgets keyed by their component key.
|
||||
@@ -87,13 +88,44 @@ public class WidgetsModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns widgets grouped by the package item that they should belong to.
|
||||
* Returns widgets (eligible for display in picker) keyed by their component key.
|
||||
*/
|
||||
public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
|
||||
if (!WIDGETS_ENABLED) {
|
||||
public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKeyForPicker() {
|
||||
if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return new HashMap<>(mWidgetsByPackageItem);
|
||||
|
||||
return mWidgetsByPackageItem.values().stream()
|
||||
.flatMap(Collection::stream).distinct()
|
||||
.filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
|
||||
.collect(Collectors.toMap(
|
||||
widget -> new ComponentKey(widget.componentName, widget.user),
|
||||
Function.identity()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns widgets (displayable in the widget picker) grouped by the package item that
|
||||
* they should belong to.
|
||||
*/
|
||||
public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItemForPicker() {
|
||||
if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return mWidgetsByPackageItem.entrySet().stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().stream()
|
||||
.filter(widgetItem ->
|
||||
mWidgetValidityCheckForPicker.test(widgetItem))
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
)
|
||||
.entrySet().stream()
|
||||
.filter(entry -> !entry.getValue().isEmpty())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,6 +213,9 @@ public class WidgetsModel {
|
||||
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
|
||||
}
|
||||
|
||||
// Refresh the validity checker with latest app state.
|
||||
mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app);
|
||||
|
||||
// Temporary cache for {@link PackageItemInfos} to avoid having to go through
|
||||
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
|
||||
PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
|
||||
@@ -195,7 +230,6 @@ public class WidgetsModel {
|
||||
|
||||
// add and update.
|
||||
mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
|
||||
.filter(new WidgetValidityCheck(app))
|
||||
.filter(new WidgetFlagCheck())
|
||||
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
|
||||
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
|
||||
@@ -270,12 +304,15 @@ public class WidgetsModel {
|
||||
return packageUserKeys;
|
||||
}
|
||||
|
||||
private static class WidgetValidityCheck implements Predicate<WidgetItem> {
|
||||
/**
|
||||
* Checks if widgets are eligible for displaying in widget picker / tray.
|
||||
*/
|
||||
private static class WidgetValidityCheckForPicker implements Predicate<WidgetItem> {
|
||||
|
||||
private final InvariantDeviceProfile mIdp;
|
||||
private final AppFilter mAppFilter;
|
||||
|
||||
WidgetValidityCheck(LauncherAppState app) {
|
||||
WidgetValidityCheckForPicker(LauncherAppState app) {
|
||||
mIdp = app.getInvariantDeviceProfile();
|
||||
mAppFilter = new AppFilter(app.getContext());
|
||||
}
|
||||
@@ -310,6 +347,10 @@ public class WidgetsModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if certain widgets that are available behind flag can be used across all surfaces in
|
||||
* launcher.
|
||||
*/
|
||||
private static class WidgetFlagCheck implements Predicate<WidgetItem> {
|
||||
|
||||
private static final String BUBBLES_SHORTCUT_WIDGET =
|
||||
|
||||
@@ -119,6 +119,11 @@ class WidgetsModelTest {
|
||||
// A widget in different package (none of that app's widgets are in widget
|
||||
// sections xml)
|
||||
createAppWidgetProviderInfo(AppBTestWidgetComponent),
|
||||
// A widget in different app that is meant to be hidden from picker
|
||||
createAppWidgetProviderInfo(
|
||||
AppCPinOnlyTestWidgetComponent,
|
||||
/*hideFromPicker=*/ true,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -129,12 +134,13 @@ class WidgetsModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
|
||||
fun widgetsByPackageForPicker_treatsWidgetSectionsAsSeparatePackageItems() {
|
||||
loadWidgets()
|
||||
|
||||
val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
|
||||
val packages: Map<PackageItemInfo, List<WidgetItem>> =
|
||||
underTest.widgetsByPackageItemForPicker
|
||||
|
||||
// expect 3 package items
|
||||
// expect 3 package items (no app C as its widget is hidden from picker)
|
||||
// one for the custom section with widget from appA
|
||||
// one for package section for second widget from appA (that wasn't listed in xml)
|
||||
// and one for package section for appB
|
||||
@@ -167,6 +173,13 @@ class WidgetsModelTest {
|
||||
assertThat(appBPackageSection).hasSize(1)
|
||||
val widgetsInAppBSection = appBPackageSection.entries.first().value
|
||||
assertThat(widgetsInAppBSection).hasSize(1)
|
||||
|
||||
// No App C's package section - as the only widget hosted by it is hidden in picker
|
||||
val appCPackageSection =
|
||||
packageSections.filter {
|
||||
it.key.packageName == AppCPinOnlyTestWidgetComponent.packageName
|
||||
}
|
||||
assertThat(appCPackageSection).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,7 +188,29 @@ class WidgetsModelTest {
|
||||
|
||||
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
|
||||
|
||||
// Has all widgets including ones not visible in picker
|
||||
assertThat(widgetsByComponentKey).hasSize(4)
|
||||
widgetsByComponentKey.forEach { entry ->
|
||||
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun widgetComponentMapForPicker_excludesWidgetsHiddenInPicker() {
|
||||
loadWidgets()
|
||||
|
||||
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> =
|
||||
underTest.widgetsByComponentKeyForPicker
|
||||
|
||||
// Has all widgets excluding the appC's widget.
|
||||
assertThat(widgetsByComponentKey).hasSize(3)
|
||||
assertThat(
|
||||
widgetsByComponentKey.filter {
|
||||
it.key.componentName == AppCPinOnlyTestWidgetComponent
|
||||
}
|
||||
)
|
||||
.isEmpty()
|
||||
// widgets mapped correctly
|
||||
widgetsByComponentKey.forEach { entry ->
|
||||
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
|
||||
}
|
||||
@@ -189,7 +224,7 @@ class WidgetsModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWidgetsByPackageItem_returnsACopyOfMap() {
|
||||
fun getWidgetsByPackageItemForPicker_returnsACopyOfMap() {
|
||||
loadWidgets()
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
@@ -198,8 +233,8 @@ class WidgetsModelTest {
|
||||
|
||||
// each "widgetsByPackageItem" read returns a different copy of the map held internally.
|
||||
// Modifying one shouldn't impact another.
|
||||
for ((_, _) in underTest.widgetsByPackageItem.entries) {
|
||||
underTest.widgetsByPackageItem.clear()
|
||||
for ((_, _) in underTest.widgetsByPackageItemForPicker.entries) {
|
||||
underTest.widgetsByPackageItemForPicker.clear()
|
||||
if (update) { // trigger update
|
||||
update = false
|
||||
// Similarly, model could update its code independently while a client is
|
||||
@@ -256,6 +291,9 @@ class WidgetsModelTest {
|
||||
private val AppBTestWidgetComponent: ComponentName =
|
||||
ComponentName.createRelative("com.test.package", "TestProvider")
|
||||
|
||||
private val AppCPinOnlyTestWidgetComponent: ComponentName =
|
||||
ComponentName.createRelative("com.testC.package", "PinOnlyTestProvider")
|
||||
|
||||
private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -83,14 +85,30 @@ public class WidgetUtils {
|
||||
|
||||
/**
|
||||
* Creates a {@link AppWidgetProviderInfo} for the provided component name
|
||||
*
|
||||
* @param cn component name of the appwidget provider
|
||||
* @param hideFromPicker indicates if the widget should appear in widget picker
|
||||
*/
|
||||
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
|
||||
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn,
|
||||
boolean hideFromPicker) {
|
||||
ActivityInfo activityInfo = new ActivityInfo();
|
||||
activityInfo.applicationInfo = new ApplicationInfo();
|
||||
activityInfo.applicationInfo.uid = Process.myUid();
|
||||
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
|
||||
if (hideFromPicker) {
|
||||
info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER;
|
||||
}
|
||||
info.providerInfo = activityInfo;
|
||||
info.provider = cn;
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link AppWidgetProviderInfo} for the provided component name
|
||||
*
|
||||
* @param cn component name of the appwidget provider
|
||||
*/
|
||||
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
|
||||
return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user