diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 955388dec7..bd2c7cc8ed 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -23,6 +23,8 @@ import static android.view.WindowInsets.Type.statusBars; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static java.util.Collections.emptyList; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ClipData; @@ -44,6 +46,7 @@ import com.android.launcher3.dragndrop.SimpleDragLayer; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetPredictionsRequester; +import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.PackageItemInfo; @@ -112,6 +115,7 @@ public class WidgetPickerActivity extends BaseActivity { private WidgetPredictionsRequester mWidgetPredictionsRequester; private final WidgetPickerDataProvider mWidgetPickerDataProvider = new WidgetPickerDataProvider(); + private WidgetsFilterDataProvider mWidgetsFilterDataProvider; private int mDesiredWidgetWidth; private int mDesiredWidgetHeight; @@ -133,13 +137,13 @@ public class WidgetPickerActivity extends BaseActivity { @Nullable private WidgetsFullSheet mWidgetSheet; - private final Predicate mWidgetsFilter = widget -> { + private final Predicate mNoShortcutsFilter = widget -> { final WidgetAcceptabilityVerdict verdict = isWidgetAcceptable(widget, /* applySizeFilter=*/ false); verdict.maybeLogVerdict(); return verdict.isAcceptable; }; - private final Predicate mDefaultWidgetsFilter = widget -> { + private final Predicate mHostSizeAndNoShortcutsFilter = widget -> { final WidgetAcceptabilityVerdict verdict = isWidgetAcceptable(widget, /* applySizeFilter=*/ true); verdict.maybeLogVerdict(); @@ -157,6 +161,7 @@ public class WidgetPickerActivity extends BaseActivity { InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); mDeviceProfile = idp.getDeviceProfile(this); mModel = new WidgetsModel(); + mWidgetsFilterDataProvider = WidgetsFilterDataProvider.Companion.newInstance(this); setContentView(R.layout.widget_picker_activity); mDragLayer = findViewById(R.id.drag_layer); @@ -288,13 +293,16 @@ public class WidgetPickerActivity extends BaseActivity { private void refreshAndBindWidgets() { MODEL_EXECUTOR.execute(() -> { LauncherAppState app = LauncherAppState.getInstance(this); + // Don't have to setup filters - its setup when launcher loads + // Just refresh filters with available cached info. + mModel.updateWidgetFilters(mWidgetsFilterDataProvider); mModel.update(app, null); StringCache stringCache = new StringCache(); stringCache.loadStrings(this); bindStringCache(stringCache); - bindWidgets(mModel.getWidgetsByPackageItem()); + bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter()); // Open sheet once widgets are available, so that it doesn't interrupt the open // animation. openWidgetsSheet(); @@ -310,14 +318,23 @@ public class WidgetPickerActivity extends BaseActivity { MAIN_EXECUTOR.execute(() -> mStringCache = stringCache); } - private void bindWidgets(Map> widgets) { + private void bindWidgets(Map> widgets, + @Nullable Predicate defaultWidgetsFilter) { WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder( mApp.getContext()); - final List allWidgets = builder.build(widgets, mWidgetsFilter); - final List defaultWidgets = - shouldShowDefaultWidgets() ? builder.build(widgets, - mDefaultWidgetsFilter) : List.of(); + final List allWidgets = builder.build(widgets, mNoShortcutsFilter); + + // Default list is shown if either defaultWidgetsFilter exists or host has additionally + // enforced size filtering. + @Nullable Predicate defaultListFilter = + hasHostSizeFilters() ? mHostSizeAndNoShortcutsFilter : null; + if (defaultWidgetsFilter != null) { + defaultListFilter = defaultListFilter != null ? defaultListFilter.and( + defaultWidgetsFilter) : defaultWidgetsFilter; + } + final List defaultWidgets = defaultListFilter != null ? builder.build( + widgets, defaultListFilter) : emptyList(); MAIN_EXECUTOR.execute( () -> mWidgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)); @@ -342,6 +359,7 @@ public class WidgetPickerActivity extends BaseActivity { @Override protected void onDestroy() { super.onDestroy(); + MODEL_EXECUTOR.execute(() -> mWidgetsFilterDataProvider.destroy()); if (mWidgetPredictionsRequester != null) { mWidgetPredictionsRequester.clear(); } @@ -398,7 +416,7 @@ public class WidgetPickerActivity extends BaseActivity { } } - private boolean shouldShowDefaultWidgets() { + private boolean hasHostSizeFilters() { // If optional filters such as size filter are present, we display them as default widgets. return mDesiredWidgetWidth != 0 || mDesiredWidgetHeight != 0; } diff --git a/res/values/config.xml b/res/values/config.xml index b0b7aa2d0a..a1ccb67a57 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -85,6 +85,9 @@ + + + 48 diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 983cf8dc50..6446f7beb7 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2629,8 +2629,9 @@ public class Launcher extends StatefulActivity * See {@code LauncherBindingDelegate} */ @Override - public void bindAllWidgets(final List allWidgets) { - mModelCallbacks.bindAllWidgets(allWidgets); + public void bindAllWidgets(@NonNull final List allWidgets, + @NonNull final List defaultWidgets) { + mModelCallbacks.bindAllWidgets(allWidgets, defaultWidgets); } @Override diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index b6da164170..01d0a740d7 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -48,6 +48,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.LauncherIconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.ModelLauncherCallbacks; +import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.InstallSessionTracker; @@ -197,7 +198,8 @@ public class LauncherAppState implements SafeCloseable { mIconProvider = new LauncherIconProvider(context); mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName, mIconProvider); - mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext), + mModel = new LauncherModel(context, this, mIconCache, + WidgetsFilterDataProvider.Companion.newInstance(context), new AppFilter(mContext), PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null); mOnTerminateCallback.add(mIconCache::close); mOnTerminateCallback.add(mModel::destroy); diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt index 85ecd58328..b56df4624c 100644 --- a/src/com/android/launcher3/LauncherModel.kt +++ b/src/com/android/launcher3/LauncherModel.kt @@ -42,6 +42,7 @@ import com.android.launcher3.model.PackageUpdatedTask import com.android.launcher3.model.ReloadStringCacheTask import com.android.launcher3.model.ShortcutsChangedTask import com.android.launcher3.model.UserLockStateChangedTask +import com.android.launcher3.model.WidgetsFilterDataProvider import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.pm.UserCache @@ -66,6 +67,7 @@ class LauncherModel( private val context: Context, private val mApp: LauncherAppState, private val iconCache: IconCache, + private val widgetsFilterDataProvider: WidgetsFilterDataProvider, appFilter: AppFilter, mPmHelper: PackageManagerHelper, isPrimaryInstance: Boolean, @@ -140,6 +142,11 @@ class LauncherModel( owner: BgDataModel.Callbacks?, ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner) + /** Returns the [WidgetsFilterDataProvider] that manages widget filters. */ + fun getWidgetsFilterDataProvider(): WidgetsFilterDataProvider { + return widgetsFilterDataProvider + } + /** Called when the icon for an app changes, outside of package event */ @WorkerThread fun onAppIconChanged(packageName: String, user: UserHandle) { @@ -160,7 +167,10 @@ class LauncherModel( /** Called when the model is destroyed */ fun destroy() { mModelDestroyed = true - MODEL_EXECUTOR.execute(modelDelegate::destroy) + MODEL_EXECUTOR.execute { + modelDelegate.destroy() + widgetsFilterDataProvider.destroy() + } } fun onBroadcastIntent(intent: Intent) { @@ -312,6 +322,7 @@ class LauncherModel( mBgDataModel, this.modelDelegate, launcherBinder, + widgetsFilterDataProvider, ) // Always post the loader task, instead of running directly @@ -417,6 +428,14 @@ class LauncherModel( } } + /** Called when the widget filters are refreshed and available to bind to the model. */ + fun onWidgetFiltersLoaded() { + enqueueModelUpdateTask { taskController, dataModel, _ -> + dataModel.widgetsModel.updateWidgetFilters(widgetsFilterDataProvider) + taskController.bindUpdatedWidgets(dataModel) + } + } + fun enqueueModelUpdateTask(task: ModelUpdateTask) { if (mModelDestroyed) { return diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt index 496d517e9a..5d3252514e 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -252,8 +252,11 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { PopupContainerWithArrow.dismissInvalidPopup(launcher) } - override fun bindAllWidgets(allWidgets: List) { - launcher.widgetPickerDataProvider.setWidgets(allWidgets, /* defaultWidgets= */ listOf()) + override fun bindAllWidgets( + allWidgets: List, + defaultWidgets: List, + ) { + launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets) } /** Returns the ids of the workspaces to bind. */ diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 1dd7d4547c..94c36c028b 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -305,7 +305,9 @@ public class PreviewSurfaceRenderer { bgModel, LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(), new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel, - /* bgAllAppsList= */ null, new Callbacks[0])) { + /* bgAllAppsList= */ null, new Callbacks[0]), + LauncherAppState.getInstance( + previewContext).getModel().getWidgetsFilterDataProvider()) { @Override public void run() { diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index b51f855308..c251114185 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -24,6 +24,8 @@ import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static java.util.Collections.emptyList; + import android.os.Process; import android.os.Trace; import android.util.Log; @@ -43,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.FixedContainerItems; 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.PackageItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -62,6 +65,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -162,9 +166,17 @@ public class BaseLauncherBinder { if (!WIDGETS_ENABLED) { return; } + Map> + widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem(); List widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext()) - .build(mBgDataModel.widgetsModel.getWidgetsByPackageItem()); - executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor); + .build(widgetsByPackageItem); + Predicate filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter(); + List defaultWidgets = + filter != null ? new WidgetsListBaseEntriesBuilder( + mApp.getContext()).build(widgetsByPackageItem, + mBgDataModel.widgetsModel.getDefaultWidgetsFilter()) : emptyList(); + + executeCallbacksTask(c -> c.bindAllWidgets(widgets, defaultWidgets), mUiExecutor); } /** diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 9a9fa5b352..b9b1e98ab9 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -537,7 +537,13 @@ public class BgDataModel { default void bindWidgetsRestored(ArrayList widgets) { } default void bindRestoreItemsChange(HashSet updates) { } default void bindWorkspaceComponentsRemoved(Predicate matcher) { } - default void bindAllWidgets(List widgets) { } + + /** + * Binds the app widgets to the providers that share widgets with the UI. + */ + default void bindAllWidgets(@NonNull List widgets, + @NonNull List defaultWidgets) { + } default void bindSmartspaceWidget() { } /** Called when workspace has been bound. */ diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 06d8b59d33..a830c969cf 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -20,6 +20,7 @@ import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN; import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed; import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; +import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker; import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; @@ -142,6 +143,7 @@ public class LoaderTask implements Runnable { private final UserManager mUserManager; private final UserCache mUserCache; private final PackageManagerHelper mPmHelper; + private final WidgetsFilterDataProvider mWidgetsFilterDataProvider; private final InstallSessionHelper mSessionHelper; private final IconCache mIconCache; @@ -158,13 +160,16 @@ public class LoaderTask implements Runnable { private String mDbName; public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, - ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) { - this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState()); + ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder, + @NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) { + this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, widgetsFilterDataProvider, + new UserManagerState()); } @VisibleForTesting LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel, ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder, + WidgetsFilterDataProvider widgetsFilterDataProvider, UserManagerState userManagerState) { mApp = app; mBgAllAppsList = bgAllAppsList; @@ -179,6 +184,7 @@ public class LoaderTask implements Runnable { mIconCache = mApp.getIconCache(); mUserManagerState = userManagerState; mInstallingPkgsCached = null; + mWidgetsFilterDataProvider = widgetsFilterDataProvider; } protected synchronized void waitForIdle() { @@ -330,7 +336,15 @@ public class LoaderTask implements Runnable { verifyNotStopped(); // fourth step - List allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null); + WidgetsModel widgetsModel = mBgDataModel.widgetsModel; + if (enableTieredWidgetsByDefaultInPicker()) { + // Begin periodic refresh of filters + mWidgetsFilterDataProvider.initPeriodicDataRefresh( + mApp.getModel()::onWidgetFiltersLoaded); + // And, update model with currently cached data. + widgetsModel.updateWidgetFilters(mWidgetsFilterDataProvider); + } + List allWidgetsList = widgetsModel.update(mApp, /*packageUser=*/null); logASplit("load widgets"); verifyNotStopped(); diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt index cf2cadc094..fc5334341b 100644 --- a/src/com/android/launcher3/model/ModelTaskController.kt +++ b/src/com/android/launcher3/model/ModelTaskController.kt @@ -35,7 +35,7 @@ class ModelTaskController( val dataModel: BgDataModel, val allAppsList: AllAppsList, private val model: LauncherModel, - private val uiExecutor: Executor + private val uiExecutor: Executor, ) { /** Schedules a {@param task} to be executed on the current callbacks. */ @@ -79,10 +79,19 @@ class ModelTaskController( } fun bindUpdatedWidgets(dataModel: BgDataModel) { - val widgets = - WidgetsListBaseEntriesBuilder(app.context) - .build(dataModel.widgetsModel.widgetsByPackageItem) - scheduleCallbackTask { it.bindAllWidgets(widgets) } + val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem + val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem) + + val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter + val defaultWidgets = + if (defaultWidgetsFilter != null) { + WidgetsListBaseEntriesBuilder(app.context) + .build(widgetsByPackageItem, defaultWidgetsFilter) + } else { + emptyList() + } + + scheduleCallbackTask { it.bindAllWidgets(allWidgets, defaultWidgets) } } fun deleteAndBindComponentsRemoved(matcher: Predicate, reason: String?) { @@ -99,7 +108,7 @@ class ModelTaskController( val packageUserKeyToUidMap = apps.associateBy( keySelector = { PackageUserKey(it.componentName!!.packageName, it.user) }, - valueTransform = { it.uid } + valueTransform = { it.uid }, ) scheduleCallbackTask { it.bindAllApplications(apps, flags, packageUserKeyToUidMap) } } diff --git a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt new file mode 100644 index 0000000000..0571de3c17 --- /dev/null +++ b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model + +import android.content.Context +import androidx.annotation.WorkerThread +import com.android.launcher3.R +import com.android.launcher3.util.ResourceBasedOverride +import java.util.function.Predicate + +/** Helper for the widgets model to load the filters that can be applied to available widgets. */ +open class WidgetsFilterDataProvider(val context: Context) : ResourceBasedOverride { + /** + * Start regular periodic refresh of widget filtering data starting now (if not started + * already). + */ + @WorkerThread + open fun initPeriodicDataRefresh(callback: WidgetsFilterLoadedCallback? = null) { + // no-op + } + + /** + * Returns a filter that should be applied to the widget predictions. + * + * @return null if no filter needs to be applied + */ + @WorkerThread open fun getPredictedWidgetsFilter(): Predicate? = null + + /** + * Returns a filter that should be applied to the widgets list to see which widgets can be shown + * by default. + * + * @return null if no separate "default" list is supported + */ + @WorkerThread open fun getDefaultWidgetsFilter(): Predicate? = null + + /** Called when filter data provider is no longer needed. */ + open fun destroy() {} + + companion object { + /** Returns a new instance of the [WidgetsFilterDataProvider] based on resource override. */ + fun newInstance(context: Context?): WidgetsFilterDataProvider { + return ResourceBasedOverride.Overrides.getObject( + WidgetsFilterDataProvider::class.java, + context, + R.string.widgets_filter_data_provider_class, + ) + } + } +} + +/** Interface for the model callback to be invoked when filters are loaded. */ +interface WidgetsFilterLoadedCallback { + /** Method called back when widget filters are loaded */ + fun onWidgetsFilterLoaded() +} diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index b450f46836..01d4996766 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -18,6 +18,8 @@ import android.os.UserHandle; import android.util.Log; import android.util.Pair; +import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.collection.ArrayMap; @@ -65,6 +67,8 @@ public class WidgetsModel { /* Map of widgets and shortcuts that are tracked per package. */ private final Map> mWidgetsByPackageItem = new HashMap<>(); + @Nullable private Predicate mDefaultWidgetsFilter = null; + @Nullable private Predicate mPredictedWidgetsFilter = null; /** * Returns all widgets keyed by their component key. @@ -91,6 +95,37 @@ public class WidgetsModel { return new HashMap<>(mWidgetsByPackageItem); } + /** + * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be + * shown in the default widgets list. + *

Returns null if filtering isn't available

+ */ + @AnyThread + public @Nullable Predicate getDefaultWidgetsFilter() { + return mDefaultWidgetsFilter; + } + + /** + * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be + * part of widget predictions. + *

Returns null if filter isn't available

+ */ + @AnyThread + public @Nullable Predicate getPredictedWidgetsFilter() { + return mPredictedWidgetsFilter; + } + + /** + * Updates model with latest filter data in cache. + */ + public void updateWidgetFilters(@NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) { + if (!WIDGETS_ENABLED) { + return; + } + mDefaultWidgetsFilter = widgetsFilterDataProvider.getDefaultWidgetsFilter(); + mPredictedWidgetsFilter = widgetsFilterDataProvider.getPredictedWidgetsFilter(); + } + /** * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. @@ -299,7 +334,7 @@ public class WidgetsModel { if (pInfo == null) { pInfo = new PackageItemInfo(key.mPackageName, key.mWidgetCategory, key.mUser); pInfo.user = key.mUser; - mMap.put(key, pInfo); + mMap.put(key, pInfo); } return pInfo; } diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt index ff545fe6ce..ae4ff042d5 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt @@ -43,6 +43,7 @@ import com.android.launcher3.widget.WidgetSections.NO_CATEGORY import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.function.Predicate import org.junit.Assert.fail import org.junit.Before import org.junit.Rule @@ -64,6 +65,7 @@ class WidgetsModelTest { @Mock private lateinit var appWidgetManager: AppWidgetManager @Mock private lateinit var app: LauncherAppState @Mock private lateinit var iconCacheMock: IconCache + @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider private lateinit var context: Context private lateinit var idp: InvariantDeviceProfile @@ -215,6 +217,27 @@ class WidgetsModelTest { // No exception } + @Test + fun updateWidgetFilters_setsFiltersCorrectly() { + val testDefaultWidgetFilter = Predicate { w -> w.widgetInfo != null } + whenever(widgetsFilterDataProvider.getDefaultWidgetsFilter()) + .thenReturn(testDefaultWidgetFilter) + val testPredicatedWidgetFilter = Predicate { w -> w.widgetInfo != null } + whenever(widgetsFilterDataProvider.getPredictedWidgetsFilter()) + .thenReturn(testPredicatedWidgetFilter) + + underTest.updateWidgetFilters(widgetsFilterDataProvider) + + assertThat(underTest.defaultWidgetsFilter).isEqualTo(testDefaultWidgetFilter) + assertThat(underTest.predictedWidgetsFilter).isEqualTo(testPredicatedWidgetFilter) + } + + @Test + fun widgetFilters_nullInitially() { + assertThat(underTest.defaultWidgetsFilter).isNull() + assertThat(underTest.predictedWidgetsFilter).isNull() + } + private fun loadWidgets() { val latch = CountDownLatch(1) Executors.MODEL_EXECUTOR.execute { diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt index ef7242f62f..882061f5b1 100644 --- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt +++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt @@ -28,6 +28,7 @@ import com.android.launcher3.util.TestUtil import com.android.launcher3.util.UserIconInfo import com.google.common.truth.Truth import java.util.concurrent.CountDownLatch +import java.util.function.Predicate import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before @@ -76,6 +77,7 @@ class LoaderTaskTest { @Mock private lateinit var modelDelegate: ModelDelegate @Mock private lateinit var launcherBinder: BaseLauncherBinder private lateinit var launcherModel: LauncherModel + @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider @Mock private lateinit var transaction: LoaderTransaction @Mock private lateinit var iconCache: IconCache @Mock private lateinit var idleLock: LooperIdleLock @@ -89,6 +91,7 @@ class LoaderTaskTest { @Before fun setup() { MockitoAnnotations.initMocks(this) + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER) launcherModel = mock(LauncherModel::class.java) mockitoSession = ExtendedMockito.mockitoSession() @@ -118,6 +121,7 @@ class LoaderTaskTest { `when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock) `when`(idleLock.awaitLocked(1000)).thenReturn(false) `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler) + `when`(widgetsFilterDataProvider.getDefaultWidgetsFilter()).thenReturn(Predicate { true }) context.putObject(UserCache.INSTANCE, userCache) TestUtil.grantWriteSecurePermission() @@ -136,17 +140,32 @@ class LoaderTaskTest { val mockUserHandles = arrayListOf(MAIN_HANDLE) `when`(userCache.userProfiles).thenReturn(mockUserHandles) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1)) - LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + this, + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() Truth.assertThat(workspaceItems.size).isAtLeast(25) Truth.assertThat(appWidgets.size).isAtLeast(7) Truth.assertThat(collections.size()).isAtLeast(8) Truth.assertThat(itemsIdMap.size()).isAtLeast(40) + Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull() } @Test fun bindsLoadedDataCorrectly() { - LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + BgDataModel(), + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() verify(launcherBinder).bindWorkspace(true, false) @@ -155,6 +174,7 @@ class LoaderTaskTest { verify(launcherBinder).bindAllApps() verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any>(), any()) verify(launcherBinder).bindDeepShortcuts() + verify(widgetsFilterDataProvider).initPeriodicDataRefresh(any()) verify(launcherBinder).bindWidgets() verify(modelDelegate).loadAndBindOtherItems(anyOrNull()) verify(iconCacheUpdateHandler).finish() @@ -172,7 +192,15 @@ class LoaderTaskTest { `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1)) - LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState) + LoaderTask( + app, + bgAllAppsList, + this, + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + userManagerState, + ) .runSyncOnBackgroundThread() verify(bgAllAppsList) @@ -193,7 +221,15 @@ class LoaderTaskTest { `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true) `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 3)) - LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState) + LoaderTask( + app, + bgAllAppsList, + this, + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + userManagerState, + ) .runSyncOnBackgroundThread() verify(bgAllAppsList) @@ -232,7 +268,14 @@ class LoaderTaskTest { RestoreDbTask.setPending(spyContext) // When - LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + BgDataModel(), + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() // Then @@ -301,7 +344,14 @@ class LoaderTaskTest { RestoreDbTask.setPending(spyContext) // When - LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + BgDataModel(), + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() // Then @@ -369,7 +419,14 @@ class LoaderTaskTest { Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) // When - LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + BgDataModel(), + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() // Then @@ -404,7 +461,14 @@ class LoaderTaskTest { RestoreDbTask.setPending(spyContext) // When - LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + LoaderTask( + app, + bgAllAppsList, + BgDataModel(), + modelDelegate, + launcherBinder, + widgetsFilterDataProvider, + ) .runSyncOnBackgroundThread() // Then