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