• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 
17 package com.android.launcher3.model;
18 
19 import android.os.Looper;
20 import android.util.Log;
21 
22 import com.android.launcher3.AllAppsList;
23 import com.android.launcher3.AppInfo;
24 import com.android.launcher3.InvariantDeviceProfile;
25 import com.android.launcher3.ItemInfo;
26 import com.android.launcher3.LauncherAppState;
27 import com.android.launcher3.LauncherAppWidgetInfo;
28 import com.android.launcher3.LauncherModel;
29 import com.android.launcher3.LauncherModel.Callbacks;
30 import com.android.launcher3.LauncherSettings;
31 import com.android.launcher3.MainThreadExecutor;
32 import com.android.launcher3.PagedView;
33 import com.android.launcher3.Utilities;
34 import com.android.launcher3.config.FeatureFlags;
35 import com.android.launcher3.util.ComponentKey;
36 import com.android.launcher3.util.LooperIdleLock;
37 import com.android.launcher3.util.MultiHashMap;
38 import com.android.launcher3.util.ViewOnDrawExecutor;
39 
40 import java.lang.ref.WeakReference;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.Set;
47 import java.util.concurrent.Executor;
48 
49 /**
50  * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
51  */
52 public class LoaderResults {
53 
54     private static final String TAG = "LoaderResults";
55     private static final long INVALID_SCREEN_ID = -1L;
56     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
57 
58     private final Executor mUiExecutor;
59 
60     private final LauncherAppState mApp;
61     private final BgDataModel mBgDataModel;
62     private final AllAppsList mBgAllAppsList;
63     private final int mPageToBindFirst;
64 
65     private final WeakReference<Callbacks> mCallbacks;
66 
LoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks)67     public LoaderResults(LauncherAppState app, BgDataModel dataModel,
68             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
69         mUiExecutor = new MainThreadExecutor();
70         mApp = app;
71         mBgDataModel = dataModel;
72         mBgAllAppsList = allAppsList;
73         mPageToBindFirst = pageToBindFirst;
74         mCallbacks = callbacks == null ? new WeakReference<Callbacks>(null) : callbacks;
75     }
76 
77     /**
78      * Binds all loaded data to actual views on the main thread.
79      */
bindWorkspace()80     public void bindWorkspace() {
81         Runnable r;
82 
83         Callbacks callbacks = mCallbacks.get();
84         // Don't use these two variables in any of the callback runnables.
85         // Otherwise we hold a reference to them.
86         if (callbacks == null) {
87             // This launcher has exited and nobody bothered to tell us.  Just bail.
88             Log.w(TAG, "LoaderTask running with no launcher");
89             return;
90         }
91 
92         // Save a copy of all the bg-thread collections
93         ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
94         ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
95         final ArrayList<Long> orderedScreenIds = new ArrayList<>();
96 
97         synchronized (mBgDataModel) {
98             workspaceItems.addAll(mBgDataModel.workspaceItems);
99             appWidgets.addAll(mBgDataModel.appWidgets);
100             orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
101         }
102 
103         final int currentScreen;
104         {
105             int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE
106                     ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen();
107             if (currScreen >= orderedScreenIds.size()) {
108                 // There may be no workspace screens (just hotseat items and an empty page).
109                 currScreen = PagedView.INVALID_RESTORE_PAGE;
110             }
111             currentScreen = currScreen;
112         }
113         final boolean validFirstPage = currentScreen >= 0;
114         final long currentScreenId =
115                 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
116 
117         // Separate the items that are on the current screen, and all the other remaining items
118         ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
119         ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
120         ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
121         ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
122 
123         filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
124                 otherWorkspaceItems);
125         filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
126                 otherAppWidgets);
127         sortWorkspaceItemsSpatially(currentWorkspaceItems);
128         sortWorkspaceItemsSpatially(otherWorkspaceItems);
129 
130         // Tell the workspace that we're about to start binding items
131         r = new Runnable() {
132             public void run() {
133                 Callbacks callbacks = mCallbacks.get();
134                 if (callbacks != null) {
135                     callbacks.clearPendingBinds();
136                     callbacks.startBinding();
137                 }
138             }
139         };
140         mUiExecutor.execute(r);
141 
142         // Bind workspace screens
143         mUiExecutor.execute(new Runnable() {
144             @Override
145             public void run() {
146                 Callbacks callbacks = mCallbacks.get();
147                 if (callbacks != null) {
148                     callbacks.bindScreens(orderedScreenIds);
149                 }
150             }
151         });
152 
153         Executor mainExecutor = mUiExecutor;
154         // Load items on the current page.
155         bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
156 
157         // In case of validFirstPage, only bind the first screen, and defer binding the
158         // remaining screens after first onDraw (and an optional the fade animation whichever
159         // happens later).
160         // This ensures that the first screen is immediately visible (eg. during rotation)
161         // In case of !validFirstPage, bind all pages one after other.
162         final Executor deferredExecutor =
163                 validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor;
164 
165         mainExecutor.execute(new Runnable() {
166             @Override
167             public void run() {
168                 Callbacks callbacks = mCallbacks.get();
169                 if (callbacks != null) {
170                     callbacks.finishFirstPageBind(
171                             validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
172                 }
173             }
174         });
175 
176         bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
177 
178         // Tell the workspace that we're done binding items
179         r = new Runnable() {
180             public void run() {
181                 Callbacks callbacks = mCallbacks.get();
182                 if (callbacks != null) {
183                     callbacks.finishBindingItems();
184                 }
185             }
186         };
187         deferredExecutor.execute(r);
188 
189         if (validFirstPage) {
190             r = new Runnable() {
191                 public void run() {
192                     Callbacks callbacks = mCallbacks.get();
193                     if (callbacks != null) {
194                         // We are loading synchronously, which means, some of the pages will be
195                         // bound after first draw. Inform the callbacks that page binding is
196                         // not complete, and schedule the remaining pages.
197                         if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
198                             callbacks.onPageBoundSynchronously(currentScreen);
199                         }
200                         callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
201                     }
202                 }
203             };
204             mUiExecutor.execute(r);
205         }
206     }
207 
208 
209     /** Filters the set of items who are directly or indirectly (via another container) on the
210      * specified screen. */
filterCurrentWorkspaceItems(long currentScreenId, ArrayList<T> allWorkspaceItems, ArrayList<T> currentScreenItems, ArrayList<T> otherScreenItems)211     private <T extends ItemInfo> void filterCurrentWorkspaceItems(long currentScreenId,
212             ArrayList<T> allWorkspaceItems,
213             ArrayList<T> currentScreenItems,
214             ArrayList<T> otherScreenItems) {
215         // Purge any null ItemInfos
216         Iterator<T> iter = allWorkspaceItems.iterator();
217         while (iter.hasNext()) {
218             ItemInfo i = iter.next();
219             if (i == null) {
220                 iter.remove();
221             }
222         }
223 
224         // Order the set of items by their containers first, this allows use to walk through the
225         // list sequentially, build up a list of containers that are in the specified screen,
226         // as well as all items in those containers.
227         Set<Long> itemsOnScreen = new HashSet<>();
228         Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
229             @Override
230             public int compare(ItemInfo lhs, ItemInfo rhs) {
231                 return Utilities.longCompare(lhs.container, rhs.container);
232             }
233         });
234         for (T info : allWorkspaceItems) {
235             if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
236                 if (info.screenId == currentScreenId) {
237                     currentScreenItems.add(info);
238                     itemsOnScreen.add(info.id);
239                 } else {
240                     otherScreenItems.add(info);
241                 }
242             } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
243                 currentScreenItems.add(info);
244                 itemsOnScreen.add(info.id);
245             } else {
246                 if (itemsOnScreen.contains(info.container)) {
247                     currentScreenItems.add(info);
248                     itemsOnScreen.add(info.id);
249                 } else {
250                     otherScreenItems.add(info);
251                 }
252             }
253         }
254     }
255 
256     /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
257      * right) */
sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)258     private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
259         final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
260         final int screenCols = profile.numColumns;
261         final int screenCellCount = profile.numColumns * profile.numRows;
262         Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
263             @Override
264             public int compare(ItemInfo lhs, ItemInfo rhs) {
265                 if (lhs.container == rhs.container) {
266                     // Within containers, order by their spatial position in that container
267                     switch ((int) lhs.container) {
268                         case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
269                             long lr = (lhs.screenId * screenCellCount +
270                                     lhs.cellY * screenCols + lhs.cellX);
271                             long rr = (rhs.screenId * screenCellCount +
272                                     rhs.cellY * screenCols + rhs.cellX);
273                             return Utilities.longCompare(lr, rr);
274                         }
275                         case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
276                             // We currently use the screen id as the rank
277                             return Utilities.longCompare(lhs.screenId, rhs.screenId);
278                         }
279                         default:
280                             if (FeatureFlags.IS_DOGFOOD_BUILD) {
281                                 throw new RuntimeException("Unexpected container type when " +
282                                         "sorting workspace items.");
283                             }
284                             return 0;
285                     }
286                 } else {
287                     // Between containers, order by hotseat, desktop
288                     return Utilities.longCompare(lhs.container, rhs.container);
289                 }
290             }
291         });
292     }
293 
bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final Executor executor)294     private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
295             final ArrayList<LauncherAppWidgetInfo> appWidgets,
296             final Executor executor) {
297 
298         // Bind the workspace items
299         int N = workspaceItems.size();
300         for (int i = 0; i < N; i += ITEMS_CHUNK) {
301             final int start = i;
302             final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
303             final Runnable r = new Runnable() {
304                 @Override
305                 public void run() {
306                     Callbacks callbacks = mCallbacks.get();
307                     if (callbacks != null) {
308                         callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
309                     }
310                 }
311             };
312             executor.execute(r);
313         }
314 
315         // Bind the widgets, one at a time
316         N = appWidgets.size();
317         for (int i = 0; i < N; i++) {
318             final ItemInfo widget = appWidgets.get(i);
319             final Runnable r = new Runnable() {
320                 public void run() {
321                     Callbacks callbacks = mCallbacks.get();
322                     if (callbacks != null) {
323                         callbacks.bindItems(Collections.singletonList(widget), false);
324                     }
325                 }
326             };
327             executor.execute(r);
328         }
329     }
330 
bindDeepShortcuts()331     public void bindDeepShortcuts() {
332         final MultiHashMap<ComponentKey, String> shortcutMapCopy;
333         synchronized (mBgDataModel) {
334             shortcutMapCopy = mBgDataModel.deepShortcutMap.clone();
335         }
336         Runnable r = new Runnable() {
337             @Override
338             public void run() {
339                 Callbacks callbacks = mCallbacks.get();
340                 if (callbacks != null) {
341                     callbacks.bindDeepShortcutMap(shortcutMapCopy);
342                 }
343             }
344         };
345         mUiExecutor.execute(r);
346     }
347 
bindAllApps()348     public void bindAllApps() {
349         // shallow copy
350         @SuppressWarnings("unchecked")
351         final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
352 
353         Runnable r = new Runnable() {
354             public void run() {
355                 Callbacks callbacks = mCallbacks.get();
356                 if (callbacks != null) {
357                     callbacks.bindAllApplications(list);
358                 }
359             }
360         };
361         mUiExecutor.execute(r);
362     }
363 
bindWidgets()364     public void bindWidgets() {
365         final MultiHashMap<PackageItemInfo, WidgetItem> widgets
366                 = mBgDataModel.widgetsModel.getWidgetsMap();
367         Runnable r = new Runnable() {
368             public void run() {
369                 Callbacks callbacks = mCallbacks.get();
370                 if (callbacks != null) {
371                     callbacks.bindAllWidgets(widgets);
372                 }
373             }
374         };
375         mUiExecutor.execute(r);
376     }
377 
newIdleLock(Object lock)378     public LooperIdleLock newIdleLock(Object lock) {
379         LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper());
380         // If we are not binding, there is no reason to wait for idle.
381         if (mCallbacks.get() == null) {
382             idleLock.queueIdle();
383         }
384         return idleLock;
385     }
386 }
387