• 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.Callbacks;
29 import com.android.launcher3.LauncherSettings;
30 import com.android.launcher3.MainThreadExecutor;
31 import com.android.launcher3.PagedView;
32 import com.android.launcher3.Utilities;
33 import com.android.launcher3.config.FeatureFlags;
34 import com.android.launcher3.util.ComponentKey;
35 import com.android.launcher3.util.LooperIdleLock;
36 import com.android.launcher3.util.MultiHashMap;
37 import com.android.launcher3.util.ViewOnDrawExecutor;
38 import com.android.launcher3.widget.WidgetListRowEntry;
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             mBgDataModel.lastBindId++;
102         }
103 
104         final int currentScreen;
105         {
106             int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE
107                     ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen();
108             if (currScreen >= orderedScreenIds.size()) {
109                 // There may be no workspace screens (just hotseat items and an empty page).
110                 currScreen = PagedView.INVALID_RESTORE_PAGE;
111             }
112             currentScreen = currScreen;
113         }
114         final boolean validFirstPage = currentScreen >= 0;
115         final long currentScreenId =
116                 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
117 
118         // Separate the items that are on the current screen, and all the other remaining items
119         ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
120         ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
121         ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
122         ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
123 
124         filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
125                 otherWorkspaceItems);
126         filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
127                 otherAppWidgets);
128         sortWorkspaceItemsSpatially(currentWorkspaceItems);
129         sortWorkspaceItemsSpatially(otherWorkspaceItems);
130 
131         // Tell the workspace that we're about to start binding items
132         r = new Runnable() {
133             public void run() {
134                 Callbacks callbacks = mCallbacks.get();
135                 if (callbacks != null) {
136                     callbacks.clearPendingBinds();
137                     callbacks.startBinding();
138                 }
139             }
140         };
141         mUiExecutor.execute(r);
142 
143         // Bind workspace screens
144         mUiExecutor.execute(new Runnable() {
145             @Override
146             public void run() {
147                 Callbacks callbacks = mCallbacks.get();
148                 if (callbacks != null) {
149                     callbacks.bindScreens(orderedScreenIds);
150                 }
151             }
152         });
153 
154         Executor mainExecutor = mUiExecutor;
155         // Load items on the current page.
156         bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
157 
158         // In case of validFirstPage, only bind the first screen, and defer binding the
159         // remaining screens after first onDraw (and an optional the fade animation whichever
160         // happens later).
161         // This ensures that the first screen is immediately visible (eg. during rotation)
162         // In case of !validFirstPage, bind all pages one after other.
163         final Executor deferredExecutor =
164                 validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
165 
166         mainExecutor.execute(new Runnable() {
167             @Override
168             public void run() {
169                 Callbacks callbacks = mCallbacks.get();
170                 if (callbacks != null) {
171                     callbacks.finishFirstPageBind(
172                             validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
173                 }
174             }
175         });
176 
177         bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
178 
179         // Tell the workspace that we're done binding items
180         r = new Runnable() {
181             public void run() {
182                 Callbacks callbacks = mCallbacks.get();
183                 if (callbacks != null) {
184                     callbacks.finishBindingItems();
185                 }
186             }
187         };
188         deferredExecutor.execute(r);
189 
190         if (validFirstPage) {
191             r = new Runnable() {
192                 public void run() {
193                     Callbacks callbacks = mCallbacks.get();
194                     if (callbacks != null) {
195                         // We are loading synchronously, which means, some of the pages will be
196                         // bound after first draw. Inform the callbacks that page binding is
197                         // not complete, and schedule the remaining pages.
198                         if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
199                             callbacks.onPageBoundSynchronously(currentScreen);
200                         }
201                         callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
202                     }
203                 }
204             };
205             mUiExecutor.execute(r);
206         }
207     }
208 
209 
210     /** Filters the set of items who are directly or indirectly (via another container) on the
211      * specified screen. */
filterCurrentWorkspaceItems(long currentScreenId, ArrayList<T> allWorkspaceItems, ArrayList<T> currentScreenItems, ArrayList<T> otherScreenItems)212     public static <T extends ItemInfo> void filterCurrentWorkspaceItems(long currentScreenId,
213             ArrayList<T> allWorkspaceItems,
214             ArrayList<T> currentScreenItems,
215             ArrayList<T> otherScreenItems) {
216         // Purge any null ItemInfos
217         Iterator<T> iter = allWorkspaceItems.iterator();
218         while (iter.hasNext()) {
219             ItemInfo i = iter.next();
220             if (i == null) {
221                 iter.remove();
222             }
223         }
224 
225         // Order the set of items by their containers first, this allows use to walk through the
226         // list sequentially, build up a list of containers that are in the specified screen,
227         // as well as all items in those containers.
228         Set<Long> itemsOnScreen = new HashSet<>();
229         Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
230             @Override
231             public int compare(ItemInfo lhs, ItemInfo rhs) {
232                 return Utilities.longCompare(lhs.container, rhs.container);
233             }
234         });
235         for (T info : allWorkspaceItems) {
236             if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
237                 if (info.screenId == currentScreenId) {
238                     currentScreenItems.add(info);
239                     itemsOnScreen.add(info.id);
240                 } else {
241                     otherScreenItems.add(info);
242                 }
243             } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
244                 currentScreenItems.add(info);
245                 itemsOnScreen.add(info.id);
246             } else {
247                 if (itemsOnScreen.contains(info.container)) {
248                     currentScreenItems.add(info);
249                     itemsOnScreen.add(info.id);
250                 } else {
251                     otherScreenItems.add(info);
252                 }
253             }
254         }
255     }
256 
257     /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
258      * right) */
sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)259     private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
260         final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
261         final int screenCols = profile.numColumns;
262         final int screenCellCount = profile.numColumns * profile.numRows;
263         Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
264             @Override
265             public int compare(ItemInfo lhs, ItemInfo rhs) {
266                 if (lhs.container == rhs.container) {
267                     // Within containers, order by their spatial position in that container
268                     switch ((int) lhs.container) {
269                         case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
270                             long lr = (lhs.screenId * screenCellCount +
271                                     lhs.cellY * screenCols + lhs.cellX);
272                             long rr = (rhs.screenId * screenCellCount +
273                                     rhs.cellY * screenCols + rhs.cellX);
274                             return Utilities.longCompare(lr, rr);
275                         }
276                         case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
277                             // We currently use the screen id as the rank
278                             return Utilities.longCompare(lhs.screenId, rhs.screenId);
279                         }
280                         default:
281                             if (FeatureFlags.IS_DOGFOOD_BUILD) {
282                                 throw new RuntimeException("Unexpected container type when " +
283                                         "sorting workspace items.");
284                             }
285                             return 0;
286                     }
287                 } else {
288                     // Between containers, order by hotseat, desktop
289                     return Utilities.longCompare(lhs.container, rhs.container);
290                 }
291             }
292         });
293     }
294 
bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final Executor executor)295     private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
296             final ArrayList<LauncherAppWidgetInfo> appWidgets,
297             final Executor executor) {
298 
299         // Bind the workspace items
300         int N = workspaceItems.size();
301         for (int i = 0; i < N; i += ITEMS_CHUNK) {
302             final int start = i;
303             final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
304             final Runnable r = new Runnable() {
305                 @Override
306                 public void run() {
307                     Callbacks callbacks = mCallbacks.get();
308                     if (callbacks != null) {
309                         callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
310                     }
311                 }
312             };
313             executor.execute(r);
314         }
315 
316         // Bind the widgets, one at a time
317         N = appWidgets.size();
318         for (int i = 0; i < N; i++) {
319             final ItemInfo widget = appWidgets.get(i);
320             final Runnable r = new Runnable() {
321                 public void run() {
322                     Callbacks callbacks = mCallbacks.get();
323                     if (callbacks != null) {
324                         callbacks.bindItems(Collections.singletonList(widget), false);
325                     }
326                 }
327             };
328             executor.execute(r);
329         }
330     }
331 
bindDeepShortcuts()332     public void bindDeepShortcuts() {
333         final MultiHashMap<ComponentKey, String> shortcutMapCopy;
334         synchronized (mBgDataModel) {
335             shortcutMapCopy = mBgDataModel.deepShortcutMap.clone();
336         }
337         Runnable r = new Runnable() {
338             @Override
339             public void run() {
340                 Callbacks callbacks = mCallbacks.get();
341                 if (callbacks != null) {
342                     callbacks.bindDeepShortcutMap(shortcutMapCopy);
343                 }
344             }
345         };
346         mUiExecutor.execute(r);
347     }
348 
bindAllApps()349     public void bindAllApps() {
350         // shallow copy
351         @SuppressWarnings("unchecked")
352         final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
353 
354         Runnable r = new Runnable() {
355             public void run() {
356                 Callbacks callbacks = mCallbacks.get();
357                 if (callbacks != null) {
358                     callbacks.bindAllApplications(list);
359                 }
360             }
361         };
362         mUiExecutor.execute(r);
363     }
364 
bindWidgets()365     public void bindWidgets() {
366         final ArrayList<WidgetListRowEntry> widgets =
367                 mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
368         Runnable r = new Runnable() {
369             public void run() {
370                 Callbacks callbacks = mCallbacks.get();
371                 if (callbacks != null) {
372                     callbacks.bindAllWidgets(widgets);
373                 }
374             }
375         };
376         mUiExecutor.execute(r);
377     }
378 
newIdleLock(Object lock)379     public LooperIdleLock newIdleLock(Object lock) {
380         LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper());
381         // If we are not binding, there is no reason to wait for idle.
382         if (mCallbacks.get() == null) {
383             idleLock.queueIdle();
384         }
385         return idleLock;
386     }
387 }
388