diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java new file mode 100644 index 0000000000..acfbea38b7 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRecentAppsController.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 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.taskbar; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.util.SparseArray; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.quickstep.RecentsModel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Provides recent apps functionality specifically in a desktop environment. + */ +public class DesktopTaskbarRecentAppsController extends TaskbarRecentAppsController { + + private final TaskbarActivityContext mContext; + private ArrayList mRunningApps = new ArrayList<>(); + private AppInfo[] mApps; + + public DesktopTaskbarRecentAppsController(TaskbarActivityContext context) { + mContext = context; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mApps = null; + } + + @Override + protected void setApps(AppInfo[] apps) { + mApps = apps; + } + + @Override + protected boolean isEnabled() { + return true; + } + + /** + * Set mRunningApps to hold currently running applications using the list of currently running + * tasks. Filtering is also done to ignore applications that are already on the taskbar in the + * original hotseat. + */ + @Override + protected void updateRunningApps(SparseArray hotseatItems) { + ArrayList runningApps = getRunningAppsFromTasks(); + ArrayList filteredRunningApps = new ArrayList<>(); + for (AppInfo runningApp : runningApps) { + boolean shouldAddOnTaskbar = true; + for (int i = 0; i < hotseatItems.size(); i++) { + if (hotseatItems.keyAt(i) >= mControllers.taskbarActivityContext.getDeviceProfile() + .numShownHotseatIcons) { + break; + } + if (hotseatItems.valueAt(i).getTargetPackage() + .equals(runningApp.getTargetPackage())) { + shouldAddOnTaskbar = false; + break; + } + } + if (shouldAddOnTaskbar) { + filteredRunningApps.add(new WorkspaceItemInfo(runningApp)); + } + } + mRunningApps = filteredRunningApps; + mControllers.taskbarViewController.commitRunningAppsToUI(); + } + + /** + * Returns a copy of hotseatItems with the addition of currently running applications. + */ + @Override + protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItemInfos) { + // hotseatItemInfos.length would be 0 if deviceProfile.numShownHotseatIcons is 0, so we + // don't want to show anything in the hotseat + if (hotseatItemInfos.length == 0) return hotseatItemInfos; + + int runningAppsIndex = 0; + ItemInfo[] newHotseatItemsInfo = Arrays.copyOf( + hotseatItemInfos, hotseatItemInfos.length + mRunningApps.size()); + for (int i = hotseatItemInfos.length; i < newHotseatItemsInfo.length; i++) { + newHotseatItemsInfo[i] = mRunningApps.get(runningAppsIndex); + runningAppsIndex++; + } + return newHotseatItemsInfo; + } + + + /** + * Returns a list of running applications from the list of currently running tasks. + */ + private ArrayList getRunningAppsFromTasks() { + ArrayList tasks = + RecentsModel.INSTANCE.get(mContext).getRunningTasks(); + ArrayList runningApps = new ArrayList<>(); + // early return if apps is empty, since we would have no AppInfo to compare + if (mApps == null) { + return runningApps; + } + + Set seenPackages = new HashSet<>(); + for (ActivityManager.RunningTaskInfo taskInfo : tasks) { + if (taskInfo.realActivity == null) continue; + + // If a different task for the same package has already been handled, skip this one + String taskPackage = taskInfo.realActivity.getPackageName(); + if (seenPackages.contains(taskPackage)) continue; + + // Otherwise, get the corresponding AppInfo and add it to the list + seenPackages.add(taskPackage); + AppInfo app = getAppInfo(taskInfo.realActivity); + if (app == null) continue; + runningApps.add(app); + } + return runningApps; + } + + /** + * Retrieves the corresponding AppInfo for the activity. + */ + private AppInfo getAppInfo(ComponentName activity) { + String packageName = activity.getPackageName(); + for (AppInfo app : mApps) { + if (!packageName.equals(app.getTargetPackage())) { + continue; + } + return app; + } + return null; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java index cf562482ed..17e8a49c83 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java @@ -32,11 +32,14 @@ public class DesktopTaskbarUIController extends TaskbarUIController { @Override protected void init(TaskbarControllers taskbarControllers) { + super.init(taskbarControllers); mLauncher.getHotseat().setIconsAlpha(0f); + mControllers.taskbarViewController.updateRunningApps(); } @Override protected void onDestroy() { + super.onDestroy(); mLauncher.getHotseat().setIconsAlpha(1f); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 5387b1ac83..f23f36541a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -168,13 +168,15 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this); + final boolean isDesktopMode = getPackageManager().hasSystemFeature(FEATURE_PC); + // Construct controllers. mControllers = new TaskbarControllers(this, new TaskbarDragController(this), buttonController, - getPackageManager().hasSystemFeature(FEATURE_PC) - ? new DesktopNavbarButtonsViewController(this, navButtonsView) : - new NavbarButtonsViewController(this, navButtonsView), + isDesktopMode + ? new DesktopNavbarButtonsViewController(this, navButtonsView) + : new NavbarButtonsViewController(this, navButtonsView), new RotationButtonController(this, c.getColor(R.color.taskbar_nav_icon_light_color), c.getColor(R.color.taskbar_nav_icon_dark_color), @@ -195,7 +197,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new TaskbarAutohideSuspendController(this), new TaskbarPopupController(this), new TaskbarForceVisibleImmersiveController(this), - new TaskbarAllAppsController(this)); + new TaskbarAllAppsController(this), + isDesktopMode + ? new DesktopTaskbarRecentAppsController(this) + : TaskbarRecentAppsController.DEFAULT); } public void init(TaskbarSharedState sharedState) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index a5999ccaac..0eab7ae2d8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -50,6 +50,7 @@ public class TaskbarControllers { public final TaskbarPopupController taskbarPopupController; public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController; public final TaskbarAllAppsController taskbarAllAppsController; + public final TaskbarRecentAppsController taskbarRecentAppsController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @@ -75,7 +76,8 @@ public class TaskbarControllers { TaskbarAutohideSuspendController taskbarAutoHideSuspendController, TaskbarPopupController taskbarPopupController, TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController, - TaskbarAllAppsController taskbarAllAppsController) { + TaskbarAllAppsController taskbarAllAppsController, + TaskbarRecentAppsController taskbarRecentAppsController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -93,6 +95,7 @@ public class TaskbarControllers { this.taskbarPopupController = taskbarPopupController; this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController; this.taskbarAllAppsController = taskbarAllAppsController; + this.taskbarRecentAppsController = taskbarRecentAppsController; } /** @@ -117,6 +120,7 @@ public class TaskbarControllers { taskbarPopupController.init(this); taskbarForceVisibleImmersiveController.init(this); taskbarAllAppsController.init(this, sharedState); + taskbarRecentAppsController.init(this); mControllersToLog = new LoggableTaskbarController[] { taskbarDragController, navButtonController, navbarButtonsViewController, @@ -153,6 +157,7 @@ public class TaskbarControllers { taskbarPopupController.onDestroy(); taskbarForceVisibleImmersiveController.onDestroy(); taskbarAllAppsController.onDestroy(); + taskbarRecentAppsController.onDestroy(); mControllersToLog = null; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java index 62392eea29..af8825e020 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -29,6 +29,7 @@ import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LauncherBindableItemsContainer; +import com.android.quickstep.RecentsModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -41,7 +42,7 @@ import java.util.List; * Launcher model Callbacks for rendering taskbar. */ public class TaskbarModelCallbacks implements - BgDataModel.Callbacks, LauncherBindableItemsContainer { + BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener { private final SparseArray mHotseatItems = new SparseArray<>(); private List mPredictedItems = Collections.emptyList(); @@ -60,6 +61,16 @@ public class TaskbarModelCallbacks implements public void init(TaskbarControllers controllers) { mControllers = controllers; + if (mControllers.taskbarRecentAppsController.isEnabled()) { + RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this); + } + } + + /** + * Unregisters listeners in this class. + */ + public void unregisterListeners() { + RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener(); } @Override @@ -184,6 +195,8 @@ public class TaskbarModelCallbacks implements isHotseatEmpty = false; } } + hotseatItemInfos = mControllers.taskbarRecentAppsController + .updateHotseatItemInfos(hotseatItemInfos); mContainer.updateHotseatItems(hotseatItemInfos); final boolean finalIsHotseatEmpty = isHotseatEmpty; @@ -194,6 +207,21 @@ public class TaskbarModelCallbacks implements }); } + @Override + public void onRunningTasksChanged() { + updateRunningApps(); + } + + /** Called when there's a change in running apps to update the UI. */ + public void commitRunningAppsToUI() { + commitItemsToUI(); + } + + /** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */ + public void updateRunningApps() { + mControllers.taskbarRecentAppsController.updateRunningApps(mHotseatItems); + } + @Override public void bindDeepShortcutMap(HashMap deepShortcutMapCopy) { mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy); @@ -202,6 +230,7 @@ public class TaskbarModelCallbacks implements @Override public void bindAllApplications(AppInfo[] apps, int flags) { mControllers.taskbarAllAppsController.setApps(apps, flags); + mControllers.taskbarRecentAppsController.setApps(apps); } protected void dumpLogs(String prefix, PrintWriter pw) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java new file mode 100644 index 0000000000..8445cff0ee --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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.taskbar; + +import android.util.SparseArray; + +import androidx.annotation.CallSuper; + +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfo; + +/** + * Base class for providing recent apps functionality + */ +public class TaskbarRecentAppsController { + + public static final TaskbarRecentAppsController DEFAULT = new TaskbarRecentAppsController(); + + // Initialized in init. + protected TaskbarControllers mControllers; + + @CallSuper + protected void init(TaskbarControllers taskbarControllers) { + mControllers = taskbarControllers; + } + + @CallSuper + protected void onDestroy() { + mControllers = null; + } + + /** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */ + protected void setApps(AppInfo[] apps) { } + + /** + * Indicates whether recent apps functionality is enabled, should return false except in + * desktop environment. + */ + protected boolean isEnabled() { + return false; + } + + /** Called to update hotseatItems, no-op except in desktop environment. */ + protected ItemInfo[] updateHotseatItemInfos(ItemInfo[] hotseatItems) { + return hotseatItems; + } + + /** Called to update the list of currently running apps, no-op except in desktop environment. */ + protected void updateRunningApps(SparseArray hotseatItems) { } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index a89061bf11..7fca0488da 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -107,6 +107,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public void onDestroy() { LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); + mModelCallbacks.unregisterListeners(); } public boolean areIconsVisible() { @@ -298,6 +299,16 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar mModelCallbacks.dumpLogs(prefix + "\t", pw); } + /** Called when there's a change in running apps to update the UI. */ + public void commitRunningAppsToUI() { + mModelCallbacks.commitRunningAppsToUI(); + } + + /** Call TaskbarModelCallbacks to update running apps. */ + public void updateRunningApps() { + mModelCallbacks.updateRunningApps(); + } + /** * Callbacks for {@link TaskbarView} to interact with its controller. */ diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 097850fd6f..d0ad221d54 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -62,6 +62,10 @@ public class RecentTasksList { private TaskLoadResult mResultsBg = INVALID_RESULT; private TaskLoadResult mResultsUi = INVALID_RESULT; + private RecentsModel.RunningTasksListener mRunningTasksListener; + // Tasks are stored in order of least recently launched to most recently launched. + private ArrayList mRunningTasks; + public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) { mMainThreadExecutor = mainThreadExecutor; @@ -73,7 +77,26 @@ public class RecentTasksList { public void onRecentTasksChanged() throws RemoteException { mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged); } + + @Override + public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + mMainThreadExecutor.execute(() -> { + RecentTasksList.this.onRunningTaskAppeared(taskInfo); + }); + } + + @Override + public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + mMainThreadExecutor.execute(() -> { + RecentTasksList.this.onRunningTaskVanished(taskInfo); + }); + } }); + // We may receive onRunningTaskAppeared events later for tasks which have already been + // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive + // onRunningTaskVanished for tasks not included in the returned list. These cases will be + // addressed when the tasks are added to/removed from mRunningTasks. + initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE)); } @VisibleForTesting @@ -154,6 +177,59 @@ public class RecentTasksList { mChangeId++; } + /** + * Registers a listener for running tasks + */ + public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) { + mRunningTasksListener = listener; + } + + /** + * Removes the previously registered running tasks listener + */ + public void unregisterRunningTasksListener() { + mRunningTasksListener = null; + } + + 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); + } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks() { + return mRunningTasks; + } + + private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + // Make sure this task is not already in the list + for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + if (taskInfo.taskId == existingTask.taskId) { + return; + } + } + mRunningTasks.add(taskInfo); + if (mRunningTasksListener != null) { + mRunningTasksListener.onRunningTasksChanged(); + } + } + + private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + // Find the task from the list of running tasks, if it exists + for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) { + if (existingTask.taskId != taskInfo.taskId) continue; + + mRunningTasks.remove(existingTask); + if (mRunningTasksListener != null) { + mRunningTasksListener.onRunningTasksChanged(); + } + return; + } + } + /** * Loads and creates a list of all the recent tasks. */ diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 5d77a6e916..964f8ea384 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -241,4 +241,35 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL */ void onTaskIconChanged(String pkg, UserHandle user); } + + /** + * Registers a listener for running tasks + */ + public void registerRunningTasksListener(RunningTasksListener listener) { + mTaskList.registerRunningTasksListener(listener); + } + + /** + * Removes the previously registered running tasks listener + */ + public void unregisterRunningTasksListener() { + mTaskList.unregisterRunningTasksListener(); + } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks() { + return mTaskList.getRunningTasks(); + } + + /** + * Listener for receiving running tasks changes + */ + public interface RunningTasksListener { + /** + * Called when there's a change to running tasks + */ + void onRunningTasksChanged(); + } } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 667ea1484d..fe474d6f82 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -20,12 +20,14 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import android.app.ActivityManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; @@ -104,12 +106,14 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI private boolean mLastNavButtonAnimate; private boolean mHasNavButtonAlphaBeenSet = false; private Runnable mPendingSetNavButtonAlpha = null; + private Context mContext; // TODO(141886704): Find a way to remove this private int mLastSystemUiStateFlags; public SystemUiProxy(Context context) { DisplayController.INSTANCE.get(context).addChangeListener(this); + mContext = context; } @Override @@ -837,4 +841,20 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } return new ArrayList<>(); } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks(int numTasks) { + if (mRecentTasks != null + && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) { + try { + return new ArrayList( + Arrays.asList(mRecentTasks.getRunningTasks(numTasks))); + } catch (RemoteException e) { + Log.w(TAG, "Failed call getRunningTasks", e); + } + } + return new ArrayList<>(); + } }