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 e2359c0d5e..3c76e8ec2d 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 36806bcf2d..2f6c2c074f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -173,13 +173,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), @@ -202,7 +204,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new TaskbarForceVisibleImmersiveController(this), new TaskbarAllAppsController(this, dp), new TaskbarInsetsController(this), - new VoiceInteractionWindowController(this)); + new VoiceInteractionWindowController(this), + isDesktopMode + ? new DesktopTaskbarRecentAppsController(this) + : TaskbarRecentAppsController.DEFAULT); } public void init(@NonNull TaskbarSharedState sharedState) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index d7b50b0994..2b80b753fb 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -53,6 +53,7 @@ public class TaskbarControllers { public final TaskbarAllAppsController taskbarAllAppsController; public final TaskbarInsetsController taskbarInsetsController; public final VoiceInteractionWindowController voiceInteractionWindowController; + public final TaskbarRecentAppsController taskbarRecentAppsController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @@ -82,7 +83,8 @@ public class TaskbarControllers { TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController, TaskbarAllAppsController taskbarAllAppsController, TaskbarInsetsController taskbarInsetsController, - VoiceInteractionWindowController voiceInteractionWindowController) { + VoiceInteractionWindowController voiceInteractionWindowController, + TaskbarRecentAppsController taskbarRecentAppsController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -102,6 +104,7 @@ public class TaskbarControllers { this.taskbarAllAppsController = taskbarAllAppsController; this.taskbarInsetsController = taskbarInsetsController; this.voiceInteractionWindowController = voiceInteractionWindowController; + this.taskbarRecentAppsController = taskbarRecentAppsController; } /** @@ -130,6 +133,7 @@ public class TaskbarControllers { navButtonController.init(this); taskbarInsetsController.init(this); voiceInteractionWindowController.init(this); + taskbarRecentAppsController.init(this); mControllersToLog = new LoggableTaskbarController[] { taskbarDragController, navButtonController, navbarButtonsViewController, @@ -178,6 +182,7 @@ public class TaskbarControllers { navButtonController.onDestroy(); taskbarInsetsController.onDestroy(); voiceInteractionWindowController.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 75881a31f2..5e670d2946 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; @@ -42,7 +43,7 @@ import java.util.function.Predicate; * 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(); @@ -61,6 +62,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 @@ -185,6 +196,8 @@ public class TaskbarModelCallbacks implements isHotseatEmpty = false; } } + hotseatItemInfos = mControllers.taskbarRecentAppsController + .updateHotseatItemInfos(hotseatItemInfos); mContainer.updateHotseatItems(hotseatItemInfos); final boolean finalIsHotseatEmpty = isHotseatEmpty; @@ -195,6 +208,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); @@ -203,6 +231,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 6d45fd2d5c..077172da18 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -126,6 +126,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public void onDestroy() { LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); + mModelCallbacks.unregisterListeners(); } public boolean areIconsVisible() { @@ -389,6 +390,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 cd93dbe509..813e687f34 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 442578eb80..3074dbb0b2 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -236,4 +236,35 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener writer.println(prefix + "RecentsModel:"); mTaskList.dump(" ", writer); } + + /** + * 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 cb80b4d979..0ec7e628b5 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; @@ -108,12 +110,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 @@ -868,4 +872,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<>(); + } }