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