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/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index e3bcb0df0d..6a908ca073 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -50,8 +50,12 @@ public class FallbackTaskbarUIController public void onStateTransitionStart(RecentsState toState) { animateToRecentsState(toState); + RecentsView recentsView = getRecentsView(); + if (recentsView == null) { + return; + } // Handle tapping on live tile. - getRecentsView().setTaskLaunchListener(toState == RecentsState.DEFAULT + recentsView.setTaskLaunchListener(toState == RecentsState.DEFAULT ? (() -> animateToRecentsState(RecentsState.BACKGROUND_APP)) : null); } @@ -81,7 +85,10 @@ public class FallbackTaskbarUIController @Override protected void onDestroy() { super.onDestroy(); - getRecentsView().setTaskLaunchListener(null); + RecentsView recentsView = getRecentsView(); + if (recentsView != null) { + recentsView.setTaskLaunchListener(null); + } mRecentsContainer.setTaskbarUIController(null); mRecentsContainer.getStateManager().removeStateListener(mStateListener); } @@ -112,7 +119,7 @@ public class FallbackTaskbarUIController } @Override - public RecentsView getRecentsView() { + public @Nullable RecentsView getRecentsView() { return mRecentsContainer.getOverviewPanel(); } diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index a5cc32a1b1..714838a468 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -20,9 +20,9 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher; -import static com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM; +import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM; -import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; import android.app.KeyguardManager; import android.app.TaskInfo; import android.content.ComponentName; @@ -40,7 +40,7 @@ import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.systemui.shared.recents.model.Task; import com.android.wm.shell.recents.IRecentTasksListener; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import java.io.PrintWriter; @@ -76,7 +76,7 @@ public class RecentTasksList { private @Nullable RecentsModel.RunningTasksListener mRunningTasksListener; private @Nullable RecentsModel.RecentTasksChangedListener mRecentTasksChangedListener; // Tasks are stored in order of least recently launched to most recently launched. - private ArrayList mRunningTasks; + private ArrayList mRunningTasks; public RecentTasksList(Context context, LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager, SystemUiProxy sysUiProxy, @@ -93,35 +93,38 @@ public class RecentTasksList { } @Override - public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + public void onRunningTaskAppeared(RunningTaskInfo taskInfo) { mMainThreadExecutor.execute(() -> { RecentTasksList.this.onRunningTaskAppeared(taskInfo); }); } @Override - public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + public void onRunningTaskVanished(RunningTaskInfo taskInfo) { mMainThreadExecutor.execute(() -> { RecentTasksList.this.onRunningTaskVanished(taskInfo); }); } @Override - public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + public void onRunningTaskChanged(RunningTaskInfo taskInfo) { mMainThreadExecutor.execute(() -> { RecentTasksList.this.onRunningTaskChanged(taskInfo); }); } @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskMovedToFront(GroupedTaskInfo[] visibleTasks) { mMainThreadExecutor.execute(() -> { - topTaskTracker.onTaskMovedToFront(taskInfo); + // TODO(b/346588978): We currently are only sending a single task, but this will + // be updated once we send the full set of visible tasks + final TaskInfo info = visibleTasks[0].getTaskInfo1(); + topTaskTracker.handleTaskMovedToFront(info); }); } @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { mMainThreadExecutor.execute(() -> topTaskTracker.onTaskChanged(taskInfo)); } }); @@ -250,7 +253,7 @@ public class RecentTasksList { mRecentTasksChangedListener = null; } - private void initRunningTasks(ArrayList runningTasks) { + private void initRunningTasks(ArrayList runningTasks) { // Tasks are retrieved in order of most recently launched/used to least recently launched. mRunningTasks = new ArrayList<>(runningTasks); Collections.reverse(mRunningTasks); @@ -259,13 +262,13 @@ public class RecentTasksList { /** * Gets the set of running tasks. */ - public ArrayList getRunningTasks() { + public ArrayList getRunningTasks() { return mRunningTasks; } - private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + private void onRunningTaskAppeared(RunningTaskInfo taskInfo) { // Make sure this task is not already in the list - for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + for (RunningTaskInfo existingTask : mRunningTasks) { if (taskInfo.taskId == existingTask.taskId) { return; } @@ -276,9 +279,9 @@ public class RecentTasksList { } } - private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + private void onRunningTaskVanished(RunningTaskInfo taskInfo) { // Find the task from the list of running tasks, if it exists - for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + for (RunningTaskInfo existingTask : mRunningTasks) { if (existingTask.taskId != taskInfo.taskId) continue; mRunningTasks.remove(existingTask); @@ -289,9 +292,9 @@ public class RecentTasksList { } } - private void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + private void onRunningTaskChanged(RunningTaskInfo taskInfo) { // Find the task from the list of running tasks, if it exists - for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + for (RunningTaskInfo existingTask : mRunningTasks) { if (existingTask.taskId != taskInfo.taskId) continue; mRunningTasks.remove(existingTask); @@ -309,7 +312,7 @@ public class RecentTasksList { @VisibleForTesting TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { int currentUserId = Process.myUserHandle().getIdentifier(); - ArrayList rawTasks; + ArrayList rawTasks; try { rawTasks = mSysUiProxy.getRecentTasks(numTasks, currentUserId); } catch (SystemUiProxy.GetRecentTasksException e) { @@ -332,7 +335,7 @@ public class RecentTasksList { TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); int numVisibleTasks = 0; - for (GroupedRecentTaskInfo rawTask : rawTasks) { + for (GroupedTaskInfo rawTask : rawTasks) { if (rawTask.getType() == TYPE_FREEFORM) { // TYPE_FREEFORM tasks is only created when desktop mode can be entered, // leftover TYPE_FREEFORM tasks created when flag was on should be ignored. @@ -344,14 +347,13 @@ public class RecentTasksList { } continue; } - ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1(); - ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2(); + TaskInfo taskInfo1 = rawTask.getTaskInfo1(); + TaskInfo taskInfo2 = rawTask.getTaskInfo2(); Task.TaskKey task1Key = new Task.TaskKey(taskInfo1); Task task1 = loadKeysOnly ? new Task(task1Key) : Task.from(task1Key, taskInfo1, tmpLockedUsers.get(task1Key.userId) /* isLocked */); - task1.setLastSnapshotData(taskInfo1); Task task2 = null; if (taskInfo2 != null) { // Is split task @@ -360,7 +362,6 @@ public class RecentTasksList { ? new Task(task2Key) : Task.from(task2Key, taskInfo2, tmpLockedUsers.get(task2Key.userId) /* isLocked */); - task2.setLastSnapshotData(taskInfo2); } else { // Is fullscreen task if (numVisibleTasks > 0) { @@ -384,17 +385,16 @@ public class RecentTasksList { return allTasks; } - private @Nullable DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) { + private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) { ArrayList tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size()); int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds(); if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) { // All Tasks are minimized -> don't create a DesktopTask return null; } - for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) { + for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) { Task.TaskKey key = new Task.TaskKey(taskInfo); Task task = Task.from(key, taskInfo, false); - task.setLastSnapshotData(taskInfo); task.positionInParent = taskInfo.positionInParent; task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds(); task.isVisible = taskInfo.isVisible; @@ -429,14 +429,14 @@ public class RecentTasksList { } writer.println(prefix + " ]"); int currentUserId = Process.myUserHandle().getIdentifier(); - ArrayList rawTasks; + ArrayList rawTasks; try { rawTasks = mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); } catch (SystemUiProxy.GetRecentTasksException e) { rawTasks = new ArrayList<>(); } writer.println(prefix + " rawTasks=["); - for (GroupedRecentTaskInfo task : rawTasks) { + for (GroupedTaskInfo task : rawTasks) { TaskInfo taskInfo1 = task.getTaskInfo1(); TaskInfo taskInfo2 = task.getTaskInfo2(); ComponentName cn1 = taskInfo1.topActivity; diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index cd39c09fee..73e22bb1c6 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -91,7 +91,7 @@ import com.android.wm.shell.recents.IRecentTasks; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.recents.IRecentsAnimationController; import com.android.wm.shell.recents.IRecentsAnimationRunner; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.IShellTransitions; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -1358,15 +1358,15 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle { * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get * RemoteException from server side */ - public ArrayList getRecentTasks(int numTasks, int userId) - throws GetRecentTasksException { + public ArrayList getRecentTasks(int numTasks, + int userId) throws GetRecentTasksException { if (mRecentTasks == null) { Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks"); throw new GetRecentTasksException("null mRecentTasks"); } try { - final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks, - RECENT_IGNORE_UNAVAILABLE, userId); + final GroupedTaskInfo[] rawTasks = + mRecentTasks.getRecentTasks(numTasks, RECENT_IGNORE_UNAVAILABLE, userId); if (rawTasks == null) { return new ArrayList<>(); } diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java index 71b657348e..c9dfe6d19d 100644 --- a/quickstep/src/com/android/quickstep/TopTaskTracker.java +++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java @@ -27,6 +27,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; import android.content.Context; import androidx.annotation.NonNull; @@ -67,7 +68,7 @@ public class TopTaskTracker extends ISplitScreenListener.Stub private static final int HISTORY_SIZE = 5; // Ordered list with first item being the most recent task. - private final LinkedList mOrderedTaskList = new LinkedList<>(); + private final LinkedList mOrderedTaskList = new LinkedList<>(); private final Context mContext; private final SplitStageInfo mMainStagePosition = new SplitStageInfo(); @@ -96,6 +97,10 @@ public class TopTaskTracker extends ISplitScreenListener.Stub @Override public void onTaskMovedToFront(RunningTaskInfo taskInfo) { + handleTaskMovedToFront(taskInfo); + } + + public void handleTaskMovedToFront(TaskInfo taskInfo) { mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId); mOrderedTaskList.addFirst(taskInfo); @@ -103,7 +108,7 @@ public class TopTaskTracker extends ISplitScreenListener.Stub // display's task to the list, to avoid showing non-home display's task upon going to // Recents animation. if (taskInfo.displayId != DEFAULT_DISPLAY) { - final RunningTaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream() + final TaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream() .filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null); if (topTaskOnHomeDisplay != null) { mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId); @@ -113,9 +118,9 @@ public class TopTaskTracker extends ISplitScreenListener.Stub if (mOrderedTaskList.size() >= HISTORY_SIZE) { // If we grow in size, remove the last taskInfo which is not part of the split task. - Iterator itr = mOrderedTaskList.descendingIterator(); + Iterator itr = mOrderedTaskList.descendingIterator(); while (itr.hasNext()) { - RunningTaskInfo info = itr.next(); + TaskInfo info = itr.next(); if (info.taskId != taskInfo.taskId && info.taskId != mMainStagePosition.taskId && info.taskId != mSideStagePosition.taskId) { @@ -215,13 +220,13 @@ public class TopTaskTracker extends ISplitScreenListener.Stub Collections.addAll(mOrderedTaskList, tasks); } - ArrayList tasks = new ArrayList<>(mOrderedTaskList); + ArrayList tasks = new ArrayList<>(mOrderedTaskList); // Strip the pinned task and recents task tasks.removeIf(t -> t.taskId == mPinnedTaskId || isRecentsTask(t)); return new CachedTaskInfo(tasks); } - private static boolean isRecentsTask(RunningTaskInfo task) { + private static boolean isRecentsTask(TaskInfo task) { return task != null && task.configuration.windowConfiguration .getActivityType() == ACTIVITY_TYPE_RECENTS; } @@ -233,10 +238,10 @@ public class TopTaskTracker extends ISplitScreenListener.Stub public static class CachedTaskInfo { @Nullable - private final RunningTaskInfo mTopTask; - public final List mAllCachedTasks; + private final TaskInfo mTopTask; + public final List mAllCachedTasks; - CachedTaskInfo(List allCachedTasks) { + CachedTaskInfo(List allCachedTasks) { mAllCachedTasks = allCachedTasks; mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0); } @@ -262,7 +267,7 @@ public class TopTaskTracker extends ISplitScreenListener.Stub // Not an excluded task. return null; } - List visibleNonExcludedTasks = mAllCachedTasks.stream() + List visibleNonExcludedTasks = mAllCachedTasks.stream() .filter(t -> t.isVisible && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0 && t.getActivityType() != ACTIVITY_TYPE_HOME diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt index 4f38ec7334..275af008e0 100644 --- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt +++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt @@ -73,13 +73,17 @@ class TasksRepository( getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId } override fun setVisibleTasks(visibleTaskIdList: Set) { - Log.d(TAG, "setVisibleTasks: $visibleTaskIdList") - // Remove tasks are no longer visible val tasksNoLongerVisible = taskRequests.keys.subtract(visibleTaskIdList) removeTasks(tasksNoLongerVisible) // Add new tasks to be requested - visibleTaskIdList.subtract(taskRequests.keys).forEach { taskId -> requestTaskData(taskId) } + val newlyVisibleTasks = visibleTaskIdList.subtract(taskRequests.keys) + newlyVisibleTasks.forEach { taskId -> requestTaskData(taskId) } + + if (tasksNoLongerVisible.isNotEmpty() || newlyVisibleTasks.isNotEmpty()) { + Log.d(TAG, "setVisibleTasks to: $visibleTaskIdList, " + + "removed: $tasksNoLongerVisible, added: $newlyVisibleTasks") + } } private fun requestTaskData(taskId: Int) { diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java index 244b897de7..b3c486c9b1 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -28,7 +28,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.KeyguardManager; +import android.app.TaskInfo; import android.content.Context; import android.content.res.Resources; @@ -39,7 +41,7 @@ import com.android.launcher3.util.LooperExecutor; import com.android.quickstep.util.GroupTask; import com.android.quickstep.views.TaskViewType; import com.android.systemui.shared.recents.model.Task; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import org.junit.Before; import org.junit.Test; @@ -91,8 +93,8 @@ public class RecentTasksListTest { @Test public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception { - GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks( - new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks( + new RecentTaskInfo(), new RecentTaskInfo(), null); when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); @@ -119,12 +121,11 @@ public class RecentTasksListTest { @Test public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception { String taskDescription = "Wheeee!"; - ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo(); + RecentTaskInfo task1 = new RecentTaskInfo(); task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); - ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo(); + RecentTaskInfo task2 = new RecentTaskInfo(); task2.taskDescription = new ActivityManager.TaskDescription(); - GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(task1, task2, - null); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, null); when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); @@ -138,11 +139,11 @@ public class RecentTasksListTest { @Test public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception { - ActivityManager.RecentTaskInfo[] tasks = { + List tasks = Arrays.asList( createRecentTaskInfo(1 /* taskId */), createRecentTaskInfo(4 /* taskId */), - createRecentTaskInfo(5 /* taskId */)}; - GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forFreeformTasks( + createRecentTaskInfo(5 /* taskId */)); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks( tasks, Collections.emptySet() /* minimizedTaskIds */); when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); @@ -162,14 +163,13 @@ public class RecentTasksListTest { @Test public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() throws Exception { - ActivityManager.RecentTaskInfo[] tasks = { + List tasks = Arrays.asList( createRecentTaskInfo(1 /* taskId */), createRecentTaskInfo(4 /* taskId */), - createRecentTaskInfo(5 /* taskId */)}; + createRecentTaskInfo(5 /* taskId */)); Set minimizedTaskIds = Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet()); - GroupedRecentTaskInfo recentTaskInfos = - GroupedRecentTaskInfo.forFreeformTasks(tasks, minimizedTaskIds); + GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(tasks, minimizedTaskIds); when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())) .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); @@ -179,8 +179,8 @@ public class RecentTasksListTest { assertEquals(0, taskList.size()); } - private ActivityManager.RecentTaskInfo createRecentTaskInfo(int taskId) { - ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); + private TaskInfo createRecentTaskInfo(int taskId) { + RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); recentTaskInfo.taskId = taskId; return recentTaskInfo; } 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/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index 0fa275e4ba..e89671e090 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -26,6 +26,7 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PluginManagerWrapper; import com.android.launcher3.util.ScreenOnTracker; import com.android.launcher3.util.SettingsCache; +import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.window.RefreshRateTracker; import com.android.launcher3.widget.custom.CustomWidgetManager; @@ -48,8 +49,9 @@ public interface LauncherBaseAppComponent { RefreshRateTracker getRefreshRateTracker(); ScreenOnTracker getScreenOnTracker(); SettingsCache getSettingsCache(); - PluginManagerWrapper getPluginManagerWrapper(); PackageManagerHelper getPackageManagerHelper(); + PluginManagerWrapper getPluginManagerWrapper(); + VibratorWrapper getVibratorWrapper(); /** Builder for LauncherBaseAppComponent. */ interface Builder { 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/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java index 29d5032cea..8fe6e93268 100644 --- a/src/com/android/launcher3/util/SettingsCache.java +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -28,6 +28,8 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import androidx.annotation.UiThread; + import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.dagger.LauncherAppSingleton; import com.android.launcher3.dagger.LauncherBaseAppComponent; @@ -140,7 +142,9 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { * Does not de-dupe if you add same listeners for the same key multiple times. * Unregister once complete using {@link #unregister(Uri, OnChangeListener)} */ + @UiThread public void register(Uri uri, OnChangeListener changeListener) { + Preconditions.assertUIThread(); if (mListenerMap.containsKey(uri)) { mListenerMap.get(uri).add(changeListener); } else { diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java index adb8f9d053..39c9c42382 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -19,6 +19,7 @@ import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK; import static android.os.VibrationEffect.createPredefined; import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.annotation.SuppressLint; @@ -31,13 +32,20 @@ import android.provider.Settings; import androidx.annotation.VisibleForTesting; +import com.android.launcher3.dagger.ApplicationContext; +import com.android.launcher3.dagger.LauncherAppSingleton; +import com.android.launcher3.dagger.LauncherBaseAppComponent; + +import javax.inject.Inject; + /** * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. */ -public class VibratorWrapper implements SafeCloseable { +@LauncherAppSingleton +public class VibratorWrapper { - public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(VibratorWrapper::new); + public static final DaggerSingletonObject INSTANCE = + new DaggerSingletonObject<>(LauncherBaseAppComponent::getVibratorWrapper); public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) @@ -59,38 +67,29 @@ public class VibratorWrapper implements SafeCloseable { private final Vibrator mVibrator; private final boolean mHasVibrator; - private final SettingsCache mSettingsCache; - @VisibleForTesting final SettingsCache.OnChangeListener mHapticChangeListener = isEnabled -> mIsHapticFeedbackEnabled = isEnabled; private boolean mIsHapticFeedbackEnabled; - private VibratorWrapper(Context context) { - this(context.getSystemService(Vibrator.class), SettingsCache.INSTANCE.get(context)); - } + @Inject + public VibratorWrapper(@ApplicationContext Context context, SettingsCache settingsCache, + DaggerSingletonTracker tracker) { - @VisibleForTesting - VibratorWrapper(Vibrator vibrator, SettingsCache settingsCache) { - mVibrator = vibrator; + mVibrator = context.getSystemService(Vibrator.class); mHasVibrator = mVibrator.hasVibrator(); - mSettingsCache = settingsCache; if (mHasVibrator) { - mSettingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener); - mIsHapticFeedbackEnabled = mSettingsCache.getValue(HAPTIC_FEEDBACK_URI, 0); + MAIN_EXECUTOR.execute( + () -> settingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener)); + mIsHapticFeedbackEnabled = settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0); + tracker.addCloseable( + () -> settingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener)); } else { mIsHapticFeedbackEnabled = false; } } - @Override - public void close() { - if (mHasVibrator) { - mSettingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener); - } - } - /** * This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For * example, when no animation is happening but a vibrator happens to be vibrating still. 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/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt index d321e4144a..dee98e7ca5 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt @@ -21,8 +21,8 @@ import android.os.VibrationEffect import android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK import android.os.VibrationEffect.Composition.PRIMITIVE_TICK import android.os.Vibrator -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext import com.android.launcher3.util.VibratorWrapper.HAPTIC_FEEDBACK_URI import com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC import com.android.launcher3.util.VibratorWrapper.VIBRATION_ATTRS @@ -41,25 +41,27 @@ import org.mockito.kotlin.never import org.mockito.kotlin.same @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(LauncherMultivalentJUnit::class) class VibratorWrapperTest { @Mock private lateinit var settingsCache: SettingsCache - @Mock private lateinit var vibrator: Vibrator + private lateinit var vibrator: Vibrator + private val context: SandboxModelContext = SandboxModelContext() @Captor private lateinit var vibrationEffectCaptor: ArgumentCaptor - + @Mock private lateinit var tracker: DaggerSingletonTracker private lateinit var underTest: VibratorWrapper @Before fun setup() { MockitoAnnotations.initMocks(this) + vibrator = context.spyService(Vibrator::class.java) `when`(settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0)).thenReturn(true) `when`(vibrator.hasVibrator()).thenReturn(true) `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_TICK)).thenReturn(true) `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)).thenReturn(true) `when`(vibrator.getPrimitiveDurations(PRIMITIVE_LOW_TICK)).thenReturn(intArrayOf(10)) - underTest = VibratorWrapper(vibrator, settingsCache) + underTest = VibratorWrapper(context, settingsCache, tracker) } @Test @@ -67,13 +69,6 @@ class VibratorWrapperTest { verify(settingsCache).register(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener) } - @Test - fun close_unregister_onChangeListener() { - underTest.close() - - verify(settingsCache).unregister(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener) - } - @Test fun vibrate() { underTest.vibrate(OVERVIEW_HAPTIC) @@ -117,7 +112,7 @@ class VibratorWrapperTest { @Test fun haptic_feedback_disabled_no_vibrate() { `when`(vibrator.hasVibrator()).thenReturn(false) - underTest = VibratorWrapper(vibrator, settingsCache) + underTest = VibratorWrapper(context, settingsCache, tracker) underTest.vibrate(OVERVIEW_HAPTIC) 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