• 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 static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
20 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
21 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
22 
23 import android.os.Process;
24 import android.util.Log;
25 
26 import com.android.launcher3.InvariantDeviceProfile;
27 import com.android.launcher3.LauncherAppState;
28 import com.android.launcher3.LauncherModel.CallbackTask;
29 import com.android.launcher3.LauncherSettings;
30 import com.android.launcher3.Workspace;
31 import com.android.launcher3.config.FeatureFlags;
32 import com.android.launcher3.model.BgDataModel.Callbacks;
33 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
34 import com.android.launcher3.model.data.AppInfo;
35 import com.android.launcher3.model.data.ItemInfo;
36 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
37 import com.android.launcher3.testing.shared.TestProtocol;
38 import com.android.launcher3.util.IntArray;
39 import com.android.launcher3.util.IntSet;
40 import com.android.launcher3.util.LooperExecutor;
41 import com.android.launcher3.util.LooperIdleLock;
42 import com.android.launcher3.util.RunnableList;
43 
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Set;
50 import java.util.concurrent.Executor;
51 
52 /**
53  * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
54  */
55 public abstract class BaseLauncherBinder {
56 
57     protected static final String TAG = "LauncherBinder";
58     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
59 
60     protected final LooperExecutor mUiExecutor;
61 
62     protected final LauncherAppState mApp;
63     protected final BgDataModel mBgDataModel;
64     private final AllAppsList mBgAllAppsList;
65 
66     private final Callbacks[] mCallbacksList;
67 
68     private int mMyBindingId;
69 
BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor)70     public BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel,
71             AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
72         mUiExecutor = uiExecutor;
73         mApp = app;
74         mBgDataModel = dataModel;
75         mBgAllAppsList = allAppsList;
76         mCallbacksList = callbacksList;
77     }
78 
79     /**
80      * Binds all loaded data to actual views on the main thread.
81      */
bindWorkspace(boolean incrementBindId)82     public void bindWorkspace(boolean incrementBindId) {
83         if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
84             DisjointWorkspaceBinder workspaceBinder =
85                     initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
86             workspaceBinder.bindCurrentWorkspacePages();
87             workspaceBinder.bindOtherWorkspacePages();
88         } else {
89             bindWorkspaceAllAtOnce(incrementBindId);
90         }
91     }
92 
93     /**
94      * Initializes the WorkspaceBinder for binding.
95      *
96      * @param incrementBindId this is used to stop previously started binding tasks that are
97      *                        obsolete but still queued.
98      * @param workspacePages this allows the Launcher to add the correct workspace screens.
99      */
initWorkspaceBinder(boolean incrementBindId, IntArray workspacePages)100     public DisjointWorkspaceBinder initWorkspaceBinder(boolean incrementBindId,
101             IntArray workspacePages) {
102 
103         synchronized (mBgDataModel) {
104             if (incrementBindId) {
105                 mBgDataModel.lastBindId++;
106             }
107             mMyBindingId = mBgDataModel.lastBindId;
108             return new DisjointWorkspaceBinder(workspacePages);
109         }
110     }
111 
bindWorkspaceAllAtOnce(boolean incrementBindId)112     private void bindWorkspaceAllAtOnce(boolean incrementBindId) {
113         // Save a copy of all the bg-thread collections
114         ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
115         ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
116         final IntArray orderedScreenIds = new IntArray();
117         ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
118 
119         synchronized (mBgDataModel) {
120             workspaceItems.addAll(mBgDataModel.workspaceItems);
121             appWidgets.addAll(mBgDataModel.appWidgets);
122             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
123             mBgDataModel.extraItems.forEach(extraItems::add);
124             if (incrementBindId) {
125                 mBgDataModel.lastBindId++;
126             }
127             mMyBindingId = mBgDataModel.lastBindId;
128         }
129 
130         for (Callbacks cb : mCallbacksList) {
131             new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
132                     workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
133         }
134     }
135 
136     /**
137      * BindDeepShortcuts is abstract because it is a no-op for the go launcher.
138      */
bindDeepShortcuts()139     public abstract void bindDeepShortcuts();
140 
141     /**
142      * Binds the all apps results from LoaderTask to the callbacks UX.
143      */
bindAllApps()144     public void bindAllApps() {
145         // shallow copy
146         AppInfo[] apps = mBgAllAppsList.copyData();
147         int flags = mBgAllAppsList.getFlags();
148         executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
149     }
150 
151     /**
152      * bindWidgets is abstract because it is a no-op for the go launcher.
153      */
bindWidgets()154     public abstract void bindWidgets();
155 
156     /**
157      * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
158      */
sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, ArrayList<ItemInfo> workspaceItems)159     protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
160             ArrayList<ItemInfo> workspaceItems) {
161         final int screenCols = profile.numColumns;
162         final int screenCellCount = profile.numColumns * profile.numRows;
163         Collections.sort(workspaceItems, (lhs, rhs) -> {
164             if (lhs.container == rhs.container) {
165                 // Within containers, order by their spatial position in that container
166                 switch (lhs.container) {
167                     case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
168                         int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
169                                 + lhs.cellX);
170                         int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
171                                 + rhs.cellX);
172                         return Integer.compare(lr, rr);
173                     }
174                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
175                         // We currently use the screen id as the rank
176                         return Integer.compare(lhs.screenId, rhs.screenId);
177                     }
178                     default:
179                         if (FeatureFlags.IS_STUDIO_BUILD) {
180                             throw new RuntimeException(
181                                     "Unexpected container type when sorting workspace items.");
182                         }
183                         return 0;
184                 }
185             } else {
186                 // Between containers, order by hotseat, desktop
187                 return Integer.compare(lhs.container, rhs.container);
188             }
189         });
190     }
191 
executeCallbacksTask(CallbackTask task, Executor executor)192     protected void executeCallbacksTask(CallbackTask task, Executor executor) {
193         executor.execute(() -> {
194             if (mMyBindingId != mBgDataModel.lastBindId) {
195                 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
196                 return;
197             }
198             for (Callbacks cb : mCallbacksList) {
199                 task.execute(cb);
200             }
201         });
202     }
203 
204     /**
205      * Only used in LoaderTask.
206      */
newIdleLock(Object lock)207     public LooperIdleLock newIdleLock(Object lock) {
208         LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
209         // If we are not binding or if the main looper is already idle, there is no reason to wait
210         if (mUiExecutor.getLooper().getQueue().isIdle()) {
211             idleLock.queueIdle();
212         }
213         return idleLock;
214     }
215 
216     private class UnifiedWorkspaceBinder {
217 
218         private final Executor mUiExecutor;
219         private final Callbacks mCallbacks;
220 
221         private final LauncherAppState mApp;
222         private final BgDataModel mBgDataModel;
223 
224         private final int mMyBindingId;
225         private final ArrayList<ItemInfo> mWorkspaceItems;
226         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
227         private final IntArray mOrderedScreenIds;
228         private final ArrayList<FixedContainerItems> mExtraItems;
229 
UnifiedWorkspaceBinder(Callbacks callbacks, Executor uiExecutor, LauncherAppState app, BgDataModel bgDataModel, int myBindingId, ArrayList<ItemInfo> workspaceItems, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<FixedContainerItems> extraItems, IntArray orderedScreenIds)230         UnifiedWorkspaceBinder(Callbacks callbacks,
231                 Executor uiExecutor,
232                 LauncherAppState app,
233                 BgDataModel bgDataModel,
234                 int myBindingId,
235                 ArrayList<ItemInfo> workspaceItems,
236                 ArrayList<LauncherAppWidgetInfo> appWidgets,
237                 ArrayList<FixedContainerItems> extraItems,
238                 IntArray orderedScreenIds) {
239             mCallbacks = callbacks;
240             mUiExecutor = uiExecutor;
241             mApp = app;
242             mBgDataModel = bgDataModel;
243             mMyBindingId = myBindingId;
244             mWorkspaceItems = workspaceItems;
245             mAppWidgets = appWidgets;
246             mExtraItems = extraItems;
247             mOrderedScreenIds = orderedScreenIds;
248         }
249 
bind()250         private void bind() {
251             final IntSet currentScreenIds =
252                     mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
253             Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks);
254 
255             // Separate the items that are on the current screen, and all the other remaining items
256             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
257             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
258             ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
259             ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
260 
261             if (TestProtocol.sDebugTracing) {
262                 Log.d(TestProtocol.NULL_INT_SET, "bind (1) currentScreenIds: "
263                         + currentScreenIds
264                         + ", pointer: "
265                         + mCallbacks
266                         + ", name: "
267                         + mCallbacks.getClass().getName());
268             }
269             filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
270                     otherWorkspaceItems);
271             if (TestProtocol.sDebugTracing) {
272                 Log.d(TestProtocol.NULL_INT_SET, "bind (2) currentScreenIds: "
273                         + currentScreenIds);
274             }
275             filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
276                     otherAppWidgets);
277             final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
278             sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
279             sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
280 
281             // Tell the workspace that we're about to start binding items
282             if (TestProtocol.sDebugTracing) {
283                 Log.d(TestProtocol.FLAKY_BINDING, "scheduling: startBinding");
284             }
285             executeCallbacksTask(c -> {
286                 c.clearPendingBinds();
287                 c.startBinding();
288             }, mUiExecutor);
289 
290             // Bind workspace screens
291             executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
292 
293             // Load items on the current page.
294             bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
295             bindAppWidgets(currentAppWidgets, mUiExecutor);
296             mExtraItems.forEach(item ->
297                     executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
298 
299             RunnableList pendingTasks = new RunnableList();
300             Executor pendingExecutor = pendingTasks::add;
301             bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
302             bindAppWidgets(otherAppWidgets, pendingExecutor);
303             if (TestProtocol.sDebugTracing) {
304                 Log.d(TestProtocol.FLAKY_BINDING, "scheduling: finishBindingItems");
305             }
306             executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
307             pendingExecutor.execute(
308                     () -> {
309                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
310                         ItemInstallQueue.INSTANCE.get(mApp.getContext())
311                                 .resumeModelPush(FLAG_LOADER_RUNNING);
312                     });
313 
314             executeCallbacksTask(
315                     c -> {
316                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
317                         c.onInitialBindComplete(currentScreenIds, pendingTasks);
318                     }, mUiExecutor);
319 
320             mCallbacks.bindStringCache(mBgDataModel.stringCache.clone());
321         }
322 
bindWorkspaceItems( final ArrayList<ItemInfo> workspaceItems, final Executor executor)323         private void bindWorkspaceItems(
324                 final ArrayList<ItemInfo> workspaceItems, final Executor executor) {
325             // Bind the workspace items
326             int count = workspaceItems.size();
327             for (int i = 0; i < count; i += ITEMS_CHUNK) {
328                 final int start = i;
329                 final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
330                 executeCallbacksTask(
331                         c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
332                         executor);
333             }
334         }
335 
bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor)336         private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) {
337             // Bind the widgets, one at a time
338             int count = appWidgets.size();
339             for (int i = 0; i < count; i++) {
340                 final ItemInfo widget = appWidgets.get(i);
341                 executeCallbacksTask(
342                         c -> c.bindItems(Collections.singletonList(widget), false), executor);
343             }
344         }
345 
executeCallbacksTask(CallbackTask task, Executor executor)346         protected void executeCallbacksTask(CallbackTask task, Executor executor) {
347             executor.execute(() -> {
348                 if (mMyBindingId != mBgDataModel.lastBindId) {
349                     Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
350                     return;
351                 }
352                 task.execute(mCallbacks);
353             });
354         }
355     }
356 
357     private class DisjointWorkspaceBinder {
358         private final IntArray mOrderedScreenIds;
359         private final IntSet mCurrentScreenIds = new IntSet();
360         private final Set<Integer> mBoundItemIds = new HashSet<>();
361 
DisjointWorkspaceBinder(IntArray orderedScreenIds)362         protected DisjointWorkspaceBinder(IntArray orderedScreenIds) {
363             mOrderedScreenIds = orderedScreenIds;
364 
365             for (Callbacks cb : mCallbacksList) {
366                 mCurrentScreenIds.addAll(cb.getPagesToBindSynchronously(orderedScreenIds));
367             }
368             if (mCurrentScreenIds.size() == 0) {
369                 mCurrentScreenIds.add(Workspace.FIRST_SCREEN_ID);
370             }
371         }
372 
373         /**
374          * Binds the currently loaded items in the Data Model. Also signals to the Callbacks[]
375          * that these items have been bound and their respective screens are ready to be shown.
376          *
377          * If this method is called after all the items on the workspace screen have already been
378          * loaded, it will bind all workspace items immediately, and bindOtherWorkspacePages() will
379          * not bind any items.
380          */
bindCurrentWorkspacePages()381         protected void bindCurrentWorkspacePages() {
382             // Save a copy of all the bg-thread collections
383             ArrayList<ItemInfo> workspaceItems;
384             ArrayList<LauncherAppWidgetInfo> appWidgets;
385 
386             synchronized (mBgDataModel) {
387                 workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
388                 appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
389             }
390 
391             workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
392             appWidgets.forEach(it -> mBoundItemIds.add(it.id));
393 
394             sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
395 
396             // Tell the workspace that we're about to start binding items
397             executeCallbacksTask(c -> {
398                 c.clearPendingBinds();
399                 c.startBinding();
400             }, mUiExecutor);
401 
402             // Bind workspace screens
403             executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
404 
405             bindWorkspaceItems(workspaceItems);
406             bindAppWidgets(appWidgets);
407 
408             executeCallbacksTask(c -> {
409                 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
410                 c.onInitialBindComplete(mCurrentScreenIds, new RunnableList());
411             }, mUiExecutor);
412         }
413 
bindOtherWorkspacePages()414         protected void bindOtherWorkspacePages() {
415             // Save a copy of all the bg-thread collections
416             ArrayList<ItemInfo> workspaceItems;
417             ArrayList<LauncherAppWidgetInfo> appWidgets;
418 
419             synchronized (mBgDataModel) {
420                 workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
421                 appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
422             }
423 
424             workspaceItems.removeIf(it -> mBoundItemIds.contains(it.id));
425             appWidgets.removeIf(it -> mBoundItemIds.contains(it.id));
426 
427             sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
428 
429             bindWorkspaceItems(workspaceItems);
430             bindAppWidgets(appWidgets);
431 
432             executeCallbacksTask(c -> c.finishBindingItems(mCurrentScreenIds), mUiExecutor);
433             mUiExecutor.execute(() -> {
434                 MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
435                 ItemInstallQueue.INSTANCE.get(mApp.getContext())
436                         .resumeModelPush(FLAG_LOADER_RUNNING);
437             });
438 
439             for (Callbacks cb : mCallbacksList) {
440                 cb.bindStringCache(mBgDataModel.stringCache.clone());
441             }
442         }
443 
bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems)444         private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems) {
445             // Bind the workspace items
446             int count = workspaceItems.size();
447             for (int i = 0; i < count; i += ITEMS_CHUNK) {
448                 final int start = i;
449                 final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
450                 executeCallbacksTask(
451                         c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
452                         mUiExecutor);
453             }
454         }
455 
bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets)456         private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets) {
457             // Bind the widgets, one at a time
458             int count = appWidgets.size();
459             for (int i = 0; i < count; i++) {
460                 final ItemInfo widget = appWidgets.get(i);
461                 executeCallbacksTask(
462                         c -> c.bindItems(Collections.singletonList(widget), false),
463                         mUiExecutor);
464             }
465         }
466     }
467 }
468