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