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