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.model.ModelUtils.sortWorkspaceItemsSpatially;
22 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
23 
24 import android.os.Process;
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.model.BgDataModel.Callbacks;
31 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
32 import com.android.launcher3.model.data.AppInfo;
33 import com.android.launcher3.model.data.ItemInfo;
34 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
35 import com.android.launcher3.testing.TestProtocol;
36 import com.android.launcher3.util.IntArray;
37 import com.android.launcher3.util.IntSet;
38 import com.android.launcher3.util.LooperExecutor;
39 import com.android.launcher3.util.LooperIdleLock;
40 import com.android.launcher3.util.RunnableList;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.concurrent.Executor;
46 
47 /**
48  * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
49  */
50 public abstract class BaseLoaderResults {
51 
52     protected static final String TAG = "LoaderResults";
53     protected static final int INVALID_SCREEN_ID = -1;
54     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
55 
56     protected final LooperExecutor mUiExecutor;
57 
58     protected final LauncherAppState mApp;
59     protected final BgDataModel mBgDataModel;
60     private final AllAppsList mBgAllAppsList;
61 
62     private final Callbacks[] mCallbacksList;
63 
64     private int mMyBindingId;
65 
BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor)66     public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
67             AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
68         mUiExecutor = uiExecutor;
69         mApp = app;
70         mBgDataModel = dataModel;
71         mBgAllAppsList = allAppsList;
72         mCallbacksList = callbacksList;
73     }
74 
75     /**
76      * Binds all loaded data to actual views on the main thread.
77      */
bindWorkspace(boolean incrementBindId)78     public void bindWorkspace(boolean incrementBindId) {
79         // Save a copy of all the bg-thread collections
80         ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
81         ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
82         final IntArray orderedScreenIds = new IntArray();
83         ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
84 
85         synchronized (mBgDataModel) {
86             workspaceItems.addAll(mBgDataModel.workspaceItems);
87             appWidgets.addAll(mBgDataModel.appWidgets);
88             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
89             mBgDataModel.extraItems.forEach(extraItems::add);
90             if (incrementBindId) {
91                 mBgDataModel.lastBindId++;
92             }
93             mMyBindingId = mBgDataModel.lastBindId;
94         }
95 
96         for (Callbacks cb : mCallbacksList) {
97             new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
98                     workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
99         }
100     }
101 
bindDeepShortcuts()102     public abstract void bindDeepShortcuts();
103 
bindAllApps()104     public void bindAllApps() {
105         // shallow copy
106         AppInfo[] apps = mBgAllAppsList.copyData();
107         int flags = mBgAllAppsList.getFlags();
108         executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
109     }
110 
bindWidgets()111     public abstract void bindWidgets();
112 
executeCallbacksTask(CallbackTask task, Executor executor)113     protected void executeCallbacksTask(CallbackTask task, Executor executor) {
114         executor.execute(() -> {
115             if (mMyBindingId != mBgDataModel.lastBindId) {
116                 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
117                 return;
118             }
119             for (Callbacks cb : mCallbacksList) {
120                 task.execute(cb);
121             }
122         });
123     }
124 
newIdleLock(Object lock)125     public LooperIdleLock newIdleLock(Object lock) {
126         LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
127         // If we are not binding or if the main looper is already idle, there is no reason to wait
128         if (mUiExecutor.getLooper().getQueue().isIdle()) {
129             idleLock.queueIdle();
130         }
131         return idleLock;
132     }
133 
134     private static class WorkspaceBinder {
135 
136         private final Executor mUiExecutor;
137         private final Callbacks mCallbacks;
138 
139         private final LauncherAppState mApp;
140         private final BgDataModel mBgDataModel;
141 
142         private final int mMyBindingId;
143         private final ArrayList<ItemInfo> mWorkspaceItems;
144         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
145         private final IntArray mOrderedScreenIds;
146         private final ArrayList<FixedContainerItems> mExtraItems;
147 
WorkspaceBinder(Callbacks callbacks, Executor uiExecutor, LauncherAppState app, BgDataModel bgDataModel, int myBindingId, ArrayList<ItemInfo> workspaceItems, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<FixedContainerItems> extraItems, IntArray orderedScreenIds)148         WorkspaceBinder(Callbacks callbacks,
149                 Executor uiExecutor,
150                 LauncherAppState app,
151                 BgDataModel bgDataModel,
152                 int myBindingId,
153                 ArrayList<ItemInfo> workspaceItems,
154                 ArrayList<LauncherAppWidgetInfo> appWidgets,
155                 ArrayList<FixedContainerItems> extraItems,
156                 IntArray orderedScreenIds) {
157             mCallbacks = callbacks;
158             mUiExecutor = uiExecutor;
159             mApp = app;
160             mBgDataModel = bgDataModel;
161             mMyBindingId = myBindingId;
162             mWorkspaceItems = workspaceItems;
163             mAppWidgets = appWidgets;
164             mExtraItems = extraItems;
165             mOrderedScreenIds = orderedScreenIds;
166         }
167 
bind()168         private void bind() {
169             IntSet currentScreenIds = mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
170 
171             // Separate the items that are on the current screen, and all the other remaining items
172             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
173             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
174             ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
175             ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
176 
177             if (TestProtocol.sDebugTracing) {
178                 Log.d(TestProtocol.NULL_INT_SET, "bind (1) currentScreenIds: "
179                         + currentScreenIds
180                         + ", pointer: "
181                         + mCallbacks
182                         + ", name: "
183                         + mCallbacks.getClass().getName());
184             }
185             filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
186                     otherWorkspaceItems);
187             if (TestProtocol.sDebugTracing) {
188                 Log.d(TestProtocol.NULL_INT_SET, "bind (2) currentScreenIds: "
189                         + currentScreenIds);
190             }
191             filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
192                     otherAppWidgets);
193             final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
194             sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
195             sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
196 
197             // Tell the workspace that we're about to start binding items
198             executeCallbacksTask(c -> {
199                 c.clearPendingBinds();
200                 c.startBinding();
201             }, mUiExecutor);
202 
203             // Bind workspace screens
204             executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
205 
206             // Load items on the current page.
207             bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
208             bindAppWidgets(currentAppWidgets, mUiExecutor);
209             mExtraItems.forEach(item ->
210                     executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
211 
212             RunnableList pendingTasks = new RunnableList();
213             Executor pendingExecutor = pendingTasks::add;
214             bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
215             bindAppWidgets(otherAppWidgets, pendingExecutor);
216             executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
217             pendingExecutor.execute(
218                     () -> {
219                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
220                         ItemInstallQueue.INSTANCE.get(mApp.getContext())
221                                 .resumeModelPush(FLAG_LOADER_RUNNING);
222                     });
223 
224             executeCallbacksTask(
225                     c -> {
226                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
227                         c.onInitialBindComplete(currentScreenIds, pendingTasks);
228                     }, mUiExecutor);
229         }
230 
bindWorkspaceItems( final ArrayList<ItemInfo> workspaceItems, final Executor executor)231         private void bindWorkspaceItems(
232                 final ArrayList<ItemInfo> workspaceItems, final Executor executor) {
233             // Bind the workspace items
234             int count = workspaceItems.size();
235             for (int i = 0; i < count; i += ITEMS_CHUNK) {
236                 final int start = i;
237                 final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
238                 executeCallbacksTask(
239                         c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
240                         executor);
241             }
242         }
243 
bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor)244         private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) {
245             // Bind the widgets, one at a time
246             int count = appWidgets.size();
247             for (int i = 0; i < count; i++) {
248                 final ItemInfo widget = appWidgets.get(i);
249                 executeCallbacksTask(
250                         c -> c.bindItems(Collections.singletonList(widget), false), executor);
251             }
252         }
253 
executeCallbacksTask(CallbackTask task, Executor executor)254         protected void executeCallbacksTask(CallbackTask task, Executor executor) {
255             executor.execute(() -> {
256                 if (mMyBindingId != mBgDataModel.lastBindId) {
257                     Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
258                     return;
259                 }
260                 task.execute(mCallbacks);
261             });
262         }
263     }
264 }
265