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.ModelUtils.filterCurrentWorkspaceItems; 20 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; 21 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; 22 23 import android.util.Log; 24 25 import com.android.launcher3.InvariantDeviceProfile; 26 import com.android.launcher3.LauncherAppState; 27 import com.android.launcher3.LauncherModel.CallbackTask; 28 import com.android.launcher3.PagedView; 29 import com.android.launcher3.model.BgDataModel.Callbacks; 30 import com.android.launcher3.model.data.AppInfo; 31 import com.android.launcher3.model.data.ItemInfo; 32 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 33 import com.android.launcher3.util.IntArray; 34 import com.android.launcher3.util.LooperExecutor; 35 import com.android.launcher3.util.LooperIdleLock; 36 import com.android.launcher3.util.ViewOnDrawExecutor; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.concurrent.Executor; 42 43 /** 44 * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}. 45 */ 46 public abstract class BaseLoaderResults { 47 48 protected static final String TAG = "LoaderResults"; 49 protected static final int INVALID_SCREEN_ID = -1; 50 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 51 52 protected final LooperExecutor mUiExecutor; 53 54 protected final LauncherAppState mApp; 55 protected final BgDataModel mBgDataModel; 56 private final AllAppsList mBgAllAppsList; 57 58 private final Callbacks[] mCallbacksList; 59 60 private int mMyBindingId; 61 BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor)62 public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, 63 AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) { 64 mUiExecutor = uiExecutor; 65 mApp = app; 66 mBgDataModel = dataModel; 67 mBgAllAppsList = allAppsList; 68 mCallbacksList = callbacksList; 69 } 70 71 /** 72 * Binds all loaded data to actual views on the main thread. 73 */ bindWorkspace()74 public void bindWorkspace() { 75 // Save a copy of all the bg-thread collections 76 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 77 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 78 final IntArray orderedScreenIds = new IntArray(); 79 80 synchronized (mBgDataModel) { 81 workspaceItems.addAll(mBgDataModel.workspaceItems); 82 appWidgets.addAll(mBgDataModel.appWidgets); 83 orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); 84 mBgDataModel.lastBindId++; 85 mMyBindingId = mBgDataModel.lastBindId; 86 } 87 88 for (Callbacks cb : mCallbacksList) { 89 new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId, 90 workspaceItems, appWidgets, orderedScreenIds).bind(); 91 } 92 } 93 bindDeepShortcuts()94 public abstract void bindDeepShortcuts(); 95 bindAllApps()96 public void bindAllApps() { 97 // shallow copy 98 AppInfo[] apps = mBgAllAppsList.copyData(); 99 int flags = mBgAllAppsList.getFlags(); 100 executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor); 101 } 102 bindWidgets()103 public abstract void bindWidgets(); 104 executeCallbacksTask(CallbackTask task, Executor executor)105 protected void executeCallbacksTask(CallbackTask task, Executor executor) { 106 executor.execute(() -> { 107 if (mMyBindingId != mBgDataModel.lastBindId) { 108 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); 109 return; 110 } 111 for (Callbacks cb : mCallbacksList) { 112 task.execute(cb); 113 } 114 }); 115 } 116 newIdleLock(Object lock)117 public LooperIdleLock newIdleLock(Object lock) { 118 LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper()); 119 // If we are not binding or if the main looper is already idle, there is no reason to wait 120 if (mUiExecutor.getLooper().getQueue().isIdle()) { 121 idleLock.queueIdle(); 122 } 123 return idleLock; 124 } 125 126 private static class WorkspaceBinder { 127 128 private final Executor mUiExecutor; 129 private final Callbacks mCallbacks; 130 131 private final LauncherAppState mApp; 132 private final BgDataModel mBgDataModel; 133 134 private final int mMyBindingId; 135 private final ArrayList<ItemInfo> mWorkspaceItems; 136 private final ArrayList<LauncherAppWidgetInfo> mAppWidgets; 137 private final IntArray mOrderedScreenIds; 138 139 WorkspaceBinder(Callbacks callbacks, Executor uiExecutor, LauncherAppState app, BgDataModel bgDataModel, int myBindingId, ArrayList<ItemInfo> workspaceItems, ArrayList<LauncherAppWidgetInfo> appWidgets, IntArray orderedScreenIds)140 WorkspaceBinder(Callbacks callbacks, 141 Executor uiExecutor, 142 LauncherAppState app, 143 BgDataModel bgDataModel, 144 int myBindingId, 145 ArrayList<ItemInfo> workspaceItems, 146 ArrayList<LauncherAppWidgetInfo> appWidgets, 147 IntArray orderedScreenIds) { 148 mCallbacks = callbacks; 149 mUiExecutor = uiExecutor; 150 mApp = app; 151 mBgDataModel = bgDataModel; 152 mMyBindingId = myBindingId; 153 mWorkspaceItems = workspaceItems; 154 mAppWidgets = appWidgets; 155 mOrderedScreenIds = orderedScreenIds; 156 } 157 bind()158 private void bind() { 159 final int currentScreen; 160 { 161 // Create an anonymous scope to calculate currentScreen as it has to be a 162 // final variable. 163 int currScreen = mCallbacks.getPageToBindSynchronously(); 164 if (currScreen >= mOrderedScreenIds.size()) { 165 // There may be no workspace screens (just hotseat items and an empty page). 166 currScreen = PagedView.INVALID_PAGE; 167 } 168 currentScreen = currScreen; 169 } 170 final boolean validFirstPage = currentScreen >= 0; 171 final int currentScreenId = 172 validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 173 174 // Separate the items that are on the current screen, and all the other remaining items 175 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 176 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 177 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 178 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 179 180 filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems, 181 otherWorkspaceItems); 182 filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets, 183 otherAppWidgets); 184 final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); 185 sortWorkspaceItemsSpatially(idp, currentWorkspaceItems); 186 sortWorkspaceItemsSpatially(idp, otherWorkspaceItems); 187 188 // Tell the workspace that we're about to start binding items 189 executeCallbacksTask(c -> { 190 c.clearPendingBinds(); 191 c.startBinding(); 192 }, mUiExecutor); 193 194 // Bind workspace screens 195 executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor); 196 197 Executor mainExecutor = mUiExecutor; 198 // Load items on the current page. 199 bindWorkspaceItems(currentWorkspaceItems, mainExecutor); 200 bindAppWidgets(currentAppWidgets, mainExecutor); 201 202 // Locate available spots for prediction using currentWorkspaceItems 203 IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons); 204 bindPredictedItems(gaps, mainExecutor); 205 // In case of validFirstPage, only bind the first screen, and defer binding the 206 // remaining screens after first onDraw (and an optional the fade animation whichever 207 // happens later). 208 // This ensures that the first screen is immediately visible (eg. during rotation) 209 // In case of !validFirstPage, bind all pages one after other. 210 final Executor deferredExecutor = 211 validFirstPage ? new ViewOnDrawExecutor() : mainExecutor; 212 213 executeCallbacksTask(c -> c.finishFirstPageBind( 214 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor); 215 216 bindWorkspaceItems(otherWorkspaceItems, deferredExecutor); 217 bindAppWidgets(otherAppWidgets, deferredExecutor); 218 // Tell the workspace that we're done binding items 219 executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor); 220 221 if (validFirstPage) { 222 executeCallbacksTask(c -> { 223 // We are loading synchronously, which means, some of the pages will be 224 // bound after first draw. Inform the mCallbacks that page binding is 225 // not complete, and schedule the remaining pages. 226 c.onPageBoundSynchronously(currentScreen); 227 c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 228 229 }, mUiExecutor); 230 } 231 } 232 bindWorkspaceItems( final ArrayList<ItemInfo> workspaceItems, final Executor executor)233 private void bindWorkspaceItems( 234 final ArrayList<ItemInfo> workspaceItems, final Executor executor) { 235 // Bind the workspace items 236 int count = workspaceItems.size(); 237 for (int i = 0; i < count; i += ITEMS_CHUNK) { 238 final int start = i; 239 final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i); 240 executeCallbacksTask( 241 c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false), 242 executor); 243 } 244 } 245 bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor)246 private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) { 247 // Bind the widgets, one at a time 248 int count = appWidgets.size(); 249 for (int i = 0; i < count; i++) { 250 final ItemInfo widget = appWidgets.get(i); 251 executeCallbacksTask( 252 c -> c.bindItems(Collections.singletonList(widget), false), executor); 253 } 254 } 255 bindPredictedItems(IntArray ranks, final Executor executor)256 private void bindPredictedItems(IntArray ranks, final Executor executor) { 257 ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems); 258 executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor); 259 } 260 executeCallbacksTask(CallbackTask task, Executor executor)261 protected void executeCallbacksTask(CallbackTask task, Executor executor) { 262 executor.execute(() -> { 263 if (mMyBindingId != mBgDataModel.lastBindId) { 264 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); 265 return; 266 } 267 task.execute(mCallbacks); 268 }); 269 } 270 } 271 } 272