1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import static com.android.window.flags.Flags.enableDesktopWindowingMode; 19 import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps; 20 21 import android.util.SparseArray; 22 import android.view.View; 23 24 import androidx.annotation.UiThread; 25 26 import com.android.launcher3.LauncherSettings.Favorites; 27 import com.android.launcher3.model.BgDataModel; 28 import com.android.launcher3.model.BgDataModel.FixedContainerItems; 29 import com.android.launcher3.model.data.AppInfo; 30 import com.android.launcher3.model.data.ItemInfo; 31 import com.android.launcher3.model.data.WorkspaceItemInfo; 32 import com.android.launcher3.statehandlers.DesktopVisibilityController; 33 import com.android.launcher3.util.ComponentKey; 34 import com.android.launcher3.util.IntArray; 35 import com.android.launcher3.util.IntSet; 36 import com.android.launcher3.util.ItemInfoMatcher; 37 import com.android.launcher3.util.LauncherBindableItemsContainer; 38 import com.android.launcher3.util.PackageUserKey; 39 import com.android.launcher3.util.Preconditions; 40 import com.android.quickstep.LauncherActivityInterface; 41 import com.android.quickstep.RecentsModel; 42 43 import java.io.PrintWriter; 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Set; 51 import java.util.function.Predicate; 52 53 /** 54 * Launcher model Callbacks for rendering taskbar. 55 */ 56 public class TaskbarModelCallbacks implements 57 BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener { 58 59 private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>(); 60 private List<ItemInfo> mPredictedItems = Collections.emptyList(); 61 62 private final TaskbarActivityContext mContext; 63 private final TaskbarView mContainer; 64 65 // Initialized in init. 66 protected TaskbarControllers mControllers; 67 68 // Used to defer any UI updates during the SUW unstash animation. 69 private boolean mDeferUpdatesForSUW; 70 private Runnable mDeferredUpdates; 71 private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener = 72 visible -> updateRunningApps(); 73 TaskbarModelCallbacks( TaskbarActivityContext context, TaskbarView container)74 public TaskbarModelCallbacks( 75 TaskbarActivityContext context, TaskbarView container) { 76 mContext = context; 77 mContainer = container; 78 } 79 init(TaskbarControllers controllers)80 public void init(TaskbarControllers controllers) { 81 mControllers = controllers; 82 if (mControllers.taskbarRecentAppsController.getCanShowRunningApps()) { 83 RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this); 84 85 if (shouldShowRunningAppsInDesktopMode()) { 86 DesktopVisibilityController desktopVisibilityController = 87 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController(); 88 if (desktopVisibilityController != null) { 89 desktopVisibilityController.registerDesktopVisibilityListener( 90 mDesktopVisibilityListener); 91 } 92 } 93 } 94 } 95 96 /** 97 * Unregisters listeners in this class. 98 */ unregisterListeners()99 public void unregisterListeners() { 100 RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener(); 101 102 if (shouldShowRunningAppsInDesktopMode()) { 103 DesktopVisibilityController desktopVisibilityController = 104 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController(); 105 if (desktopVisibilityController != null) { 106 desktopVisibilityController.unregisterDesktopVisibilityListener( 107 mDesktopVisibilityListener); 108 } 109 } 110 } 111 shouldShowRunningAppsInDesktopMode()112 private boolean shouldShowRunningAppsInDesktopMode() { 113 // TODO(b/335401172): unify DesktopMode checks in Launcher 114 return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps(); 115 } 116 117 @Override startBinding()118 public void startBinding() { 119 mContext.setBindingItems(true); 120 mHotseatItems.clear(); 121 mPredictedItems = Collections.emptyList(); 122 } 123 124 @Override finishBindingItems(IntSet pagesBoundFirst)125 public void finishBindingItems(IntSet pagesBoundFirst) { 126 mContext.setBindingItems(false); 127 commitItemsToUI(); 128 } 129 130 @Override bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)131 public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, 132 ArrayList<ItemInfo> addAnimated) { 133 boolean add1 = handleItemsAdded(addNotAnimated); 134 boolean add2 = handleItemsAdded(addAnimated); 135 if (add1 || add2) { 136 commitItemsToUI(); 137 } 138 } 139 140 @Override bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)141 public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { 142 if (handleItemsAdded(shortcuts)) { 143 commitItemsToUI(); 144 } 145 } 146 handleItemsAdded(List<ItemInfo> items)147 private boolean handleItemsAdded(List<ItemInfo> items) { 148 boolean modified = false; 149 for (ItemInfo item : items) { 150 if (item.container == Favorites.CONTAINER_HOTSEAT) { 151 mHotseatItems.put(item.screenId, item); 152 modified = true; 153 } 154 } 155 return modified; 156 } 157 158 159 @Override bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated)160 public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { 161 updateWorkspaceItems(updated, mContext); 162 } 163 164 @Override bindRestoreItemsChange(HashSet<ItemInfo> updates)165 public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { 166 updateRestoreItems(updates, mContext); 167 } 168 169 @Override mapOverItems(ItemOperator op)170 public void mapOverItems(ItemOperator op) { 171 final int itemCount = mContainer.getChildCount(); 172 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { 173 View item = mContainer.getChildAt(itemIdx); 174 if (op.evaluate((ItemInfo) item.getTag(), item)) { 175 return; 176 } 177 } 178 } 179 180 @Override bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher)181 public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { 182 if (handleItemsRemoved(matcher)) { 183 commitItemsToUI(); 184 } 185 } 186 handleItemsRemoved(Predicate<ItemInfo> matcher)187 private boolean handleItemsRemoved(Predicate<ItemInfo> matcher) { 188 boolean modified = false; 189 for (int i = mHotseatItems.size() - 1; i >= 0; i--) { 190 if (matcher.test(mHotseatItems.valueAt(i))) { 191 modified = true; 192 mHotseatItems.removeAt(i); 193 } 194 } 195 return modified; 196 } 197 198 @Override bindItemsModified(List<ItemInfo> items)199 public void bindItemsModified(List<ItemInfo> items) { 200 boolean removed = handleItemsRemoved(ItemInfoMatcher.ofItems(items)); 201 boolean added = handleItemsAdded(items); 202 if (removed || added) { 203 commitItemsToUI(); 204 } 205 } 206 207 @Override bindExtraContainerItems(FixedContainerItems item)208 public void bindExtraContainerItems(FixedContainerItems item) { 209 if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) { 210 mPredictedItems = item.items; 211 commitItemsToUI(); 212 } else if (item.containerId == Favorites.CONTAINER_PREDICTION) { 213 mControllers.taskbarAllAppsController.setPredictedApps(item.items); 214 } 215 } 216 commitItemsToUI()217 private void commitItemsToUI() { 218 if (mContext.isBindingItems()) { 219 return; 220 } 221 222 ItemInfo[] hotseatItemInfos = 223 new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons]; 224 int predictionSize = mPredictedItems.size(); 225 int predictionNextIndex = 0; 226 227 for (int i = 0; i < hotseatItemInfos.length; i++) { 228 hotseatItemInfos[i] = mHotseatItems.get(i); 229 if (hotseatItemInfos[i] == null && predictionNextIndex < predictionSize) { 230 hotseatItemInfos[i] = mPredictedItems.get(predictionNextIndex); 231 hotseatItemInfos[i].screenId = i; 232 predictionNextIndex++; 233 } 234 } 235 hotseatItemInfos = mControllers.taskbarRecentAppsController 236 .updateHotseatItemInfos(hotseatItemInfos); 237 Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps(); 238 Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps(); 239 240 if (mDeferUpdatesForSUW) { 241 ItemInfo[] finalHotseatItemInfos = hotseatItemInfos; 242 mDeferredUpdates = () -> 243 commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages, 244 minimizedPackages); 245 } else { 246 commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages); 247 } 248 } 249 commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages, Set<String> minimizedPackages)250 private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages, 251 Set<String> minimizedPackages) { 252 mContainer.updateHotseatItems(hotseatItemInfos); 253 mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages, 254 minimizedPackages); 255 } 256 257 /** 258 * This is used to defer UI updates after SUW builds the unstash animation. 259 * @param defer if true, defers updates to the UI 260 * if false, posts updates (if any) to the UI 261 */ setDeferUpdatesForSUW(boolean defer)262 public void setDeferUpdatesForSUW(boolean defer) { 263 mDeferUpdatesForSUW = defer; 264 265 if (!mDeferUpdatesForSUW) { 266 if (mDeferredUpdates != null) { 267 mContainer.post(mDeferredUpdates); 268 mDeferredUpdates = null; 269 } 270 } 271 } 272 273 @Override onRunningTasksChanged()274 public void onRunningTasksChanged() { 275 updateRunningApps(); 276 } 277 278 /** Called when there's a change in running apps to update the UI. */ commitRunningAppsToUI()279 public void commitRunningAppsToUI() { 280 commitItemsToUI(); 281 } 282 283 /** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */ updateRunningApps()284 public void updateRunningApps() { 285 mControllers.taskbarRecentAppsController.updateRunningApps(); 286 } 287 288 @Override bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy)289 public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) { 290 mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy); 291 } 292 293 @UiThread 294 @Override bindAllApplications(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> packageUserKeytoUidMap)295 public void bindAllApplications(AppInfo[] apps, int flags, 296 Map<PackageUserKey, Integer> packageUserKeytoUidMap) { 297 Preconditions.assertUIThread(); 298 mControllers.taskbarAllAppsController.setApps(apps, flags, packageUserKeytoUidMap); 299 mControllers.taskbarRecentAppsController.setApps(apps); 300 } 301 dumpLogs(String prefix, PrintWriter pw)302 protected void dumpLogs(String prefix, PrintWriter pw) { 303 pw.println(prefix + "TaskbarModelCallbacks:"); 304 305 pw.println(String.format("%s\thotseat items count=%s", prefix, mHotseatItems.size())); 306 if (mPredictedItems != null) { 307 pw.println( 308 String.format("%s\tpredicted items count=%s", prefix, mPredictedItems.size())); 309 } 310 pw.println(String.format("%s\tmDeferUpdatesForSUW=%b", prefix, mDeferUpdatesForSUW)); 311 pw.println(String.format("%s\tupdates pending=%b", prefix, (mDeferredUpdates != null))); 312 } 313 } 314