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.util.Executors.MAIN_EXECUTOR; 20 21 import android.os.Looper; 22 import android.util.Log; 23 24 import com.android.launcher3.AppInfo; 25 import com.android.launcher3.InvariantDeviceProfile; 26 import com.android.launcher3.ItemInfo; 27 import com.android.launcher3.LauncherAppState; 28 import com.android.launcher3.LauncherAppWidgetInfo; 29 import com.android.launcher3.LauncherModel.CallbackTask; 30 import com.android.launcher3.LauncherSettings; 31 import com.android.launcher3.PagedView; 32 import com.android.launcher3.config.FeatureFlags; 33 import com.android.launcher3.model.BgDataModel.Callbacks; 34 import com.android.launcher3.util.IntArray; 35 import com.android.launcher3.util.IntSet; 36 import com.android.launcher3.util.LooperIdleLock; 37 import com.android.launcher3.util.ViewOnDrawExecutor; 38 39 import java.lang.ref.WeakReference; 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.Comparator; 43 import java.util.Iterator; 44 import java.util.concurrent.Executor; 45 46 /** 47 * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}. 48 */ 49 public abstract class BaseLoaderResults { 50 51 protected static final String TAG = "LoaderResults"; 52 protected static final int INVALID_SCREEN_ID = -1; 53 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 54 55 protected final Executor mUiExecutor; 56 57 protected final LauncherAppState mApp; 58 protected final BgDataModel mBgDataModel; 59 private final AllAppsList mBgAllAppsList; 60 protected final int mPageToBindFirst; 61 62 protected final WeakReference<Callbacks> mCallbacks; 63 64 private int mMyBindingId; 65 BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks)66 public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, 67 AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) { 68 mUiExecutor = MAIN_EXECUTOR; 69 mApp = app; 70 mBgDataModel = dataModel; 71 mBgAllAppsList = allAppsList; 72 mPageToBindFirst = pageToBindFirst; 73 mCallbacks = callbacks == null ? new WeakReference<>(null) : callbacks; 74 } 75 76 /** 77 * Binds all loaded data to actual views on the main thread. 78 */ bindWorkspace()79 public void bindWorkspace() { 80 Callbacks callbacks = mCallbacks.get(); 81 // Don't use these two variables in any of the callback runnables. 82 // Otherwise we hold a reference to them. 83 if (callbacks == null) { 84 // This launcher has exited and nobody bothered to tell us. Just bail. 85 Log.w(TAG, "LoaderTask running with no launcher"); 86 return; 87 } 88 89 // Save a copy of all the bg-thread collections 90 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 91 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 92 final IntArray orderedScreenIds = new IntArray(); 93 94 synchronized (mBgDataModel) { 95 workspaceItems.addAll(mBgDataModel.workspaceItems); 96 appWidgets.addAll(mBgDataModel.appWidgets); 97 orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); 98 mBgDataModel.lastBindId++; 99 mMyBindingId = mBgDataModel.lastBindId; 100 } 101 102 final int currentScreen; 103 { 104 int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE 105 ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen(); 106 if (currScreen >= orderedScreenIds.size()) { 107 // There may be no workspace screens (just hotseat items and an empty page). 108 currScreen = PagedView.INVALID_RESTORE_PAGE; 109 } 110 currentScreen = currScreen; 111 } 112 final boolean validFirstPage = currentScreen >= 0; 113 final int currentScreenId = 114 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 115 116 // Separate the items that are on the current screen, and all the other remaining items 117 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 118 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 119 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 120 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 121 122 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 123 otherWorkspaceItems); 124 filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets, 125 otherAppWidgets); 126 sortWorkspaceItemsSpatially(currentWorkspaceItems); 127 sortWorkspaceItemsSpatially(otherWorkspaceItems); 128 129 // Tell the workspace that we're about to start binding items 130 executeCallbacksTask(c -> { 131 c.clearPendingBinds(); 132 c.startBinding(); 133 }, mUiExecutor); 134 135 // Bind workspace screens 136 executeCallbacksTask(c -> c.bindScreens(orderedScreenIds), mUiExecutor); 137 138 Executor mainExecutor = mUiExecutor; 139 // Load items on the current page. 140 bindWorkspaceItems(currentWorkspaceItems, mainExecutor); 141 bindAppWidgets(currentAppWidgets, mainExecutor); 142 // In case of validFirstPage, only bind the first screen, and defer binding the 143 // remaining screens after first onDraw (and an optional the fade animation whichever 144 // happens later). 145 // This ensures that the first screen is immediately visible (eg. during rotation) 146 // In case of !validFirstPage, bind all pages one after other. 147 final Executor deferredExecutor = 148 validFirstPage ? new ViewOnDrawExecutor() : mainExecutor; 149 150 executeCallbacksTask(c -> c.finishFirstPageBind( 151 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor); 152 153 bindWorkspaceItems(otherWorkspaceItems, deferredExecutor); 154 bindAppWidgets(otherAppWidgets, deferredExecutor); 155 // Tell the workspace that we're done binding items 156 executeCallbacksTask(c -> c.finishBindingItems(mPageToBindFirst), deferredExecutor); 157 158 if (validFirstPage) { 159 executeCallbacksTask(c -> { 160 // We are loading synchronously, which means, some of the pages will be 161 // bound after first draw. Inform the callbacks that page binding is 162 // not complete, and schedule the remaining pages. 163 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 164 c.onPageBoundSynchronously(currentScreen); 165 } 166 c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 167 168 }, mUiExecutor); 169 } 170 } 171 172 173 /** Filters the set of items who are directly or indirectly (via another container) on the 174 * specified screen. */ filterCurrentWorkspaceItems(int currentScreenId, ArrayList<T> allWorkspaceItems, ArrayList<T> currentScreenItems, ArrayList<T> otherScreenItems)175 public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId, 176 ArrayList<T> allWorkspaceItems, 177 ArrayList<T> currentScreenItems, 178 ArrayList<T> otherScreenItems) { 179 // Purge any null ItemInfos 180 Iterator<T> iter = allWorkspaceItems.iterator(); 181 while (iter.hasNext()) { 182 ItemInfo i = iter.next(); 183 if (i == null) { 184 iter.remove(); 185 } 186 } 187 188 // Order the set of items by their containers first, this allows use to walk through the 189 // list sequentially, build up a list of containers that are in the specified screen, 190 // as well as all items in those containers. 191 IntSet itemsOnScreen = new IntSet(); 192 Collections.sort(allWorkspaceItems, 193 (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); 194 195 for (T info : allWorkspaceItems) { 196 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 197 if (info.screenId == currentScreenId) { 198 currentScreenItems.add(info); 199 itemsOnScreen.add(info.id); 200 } else { 201 otherScreenItems.add(info); 202 } 203 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 204 currentScreenItems.add(info); 205 itemsOnScreen.add(info.id); 206 } else { 207 if (itemsOnScreen.contains(info.container)) { 208 currentScreenItems.add(info); 209 itemsOnScreen.add(info.id); 210 } else { 211 otherScreenItems.add(info); 212 } 213 } 214 } 215 } 216 217 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 218 * right) */ sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)219 protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 220 final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); 221 final int screenCols = profile.numColumns; 222 final int screenCellCount = profile.numColumns * profile.numRows; 223 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 224 @Override 225 public int compare(ItemInfo lhs, ItemInfo rhs) { 226 if (lhs.container == rhs.container) { 227 // Within containers, order by their spatial position in that container 228 switch (lhs.container) { 229 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 230 int lr = (lhs.screenId * screenCellCount + 231 lhs.cellY * screenCols + lhs.cellX); 232 int rr = (rhs.screenId * screenCellCount + 233 rhs.cellY * screenCols + rhs.cellX); 234 return Integer.compare(lr, rr); 235 } 236 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 237 // We currently use the screen id as the rank 238 return Integer.compare(lhs.screenId, rhs.screenId); 239 } 240 default: 241 if (FeatureFlags.IS_DOGFOOD_BUILD) { 242 throw new RuntimeException("Unexpected container type when " + 243 "sorting workspace items."); 244 } 245 return 0; 246 } 247 } else { 248 // Between containers, order by hotseat, desktop 249 return Integer.compare(lhs.container, rhs.container); 250 } 251 } 252 }); 253 } 254 bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, final Executor executor)255 protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, 256 final Executor executor) { 257 // Bind the workspace items 258 int N = workspaceItems.size(); 259 for (int i = 0; i < N; i += ITEMS_CHUNK) { 260 final int start = i; 261 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 262 executeCallbacksTask( 263 c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false), 264 executor); 265 } 266 } 267 bindAppWidgets(ArrayList<LauncherAppWidgetInfo> appWidgets, Executor executor)268 private void bindAppWidgets(ArrayList<LauncherAppWidgetInfo> appWidgets, Executor executor) { 269 int N;// Bind the widgets, one at a time 270 N = appWidgets.size(); 271 for (int i = 0; i < N; i++) { 272 final ItemInfo widget = appWidgets.get(i); 273 executeCallbacksTask( 274 c -> c.bindItems(Collections.singletonList(widget), false), executor); 275 } 276 } 277 bindDeepShortcuts()278 public abstract void bindDeepShortcuts(); 279 bindAllApps()280 public void bindAllApps() { 281 // shallow copy 282 AppInfo[] apps = mBgAllAppsList.copyData(); 283 executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor); 284 } 285 bindWidgets()286 public abstract void bindWidgets(); 287 executeCallbacksTask(CallbackTask task, Executor executor)288 protected void executeCallbacksTask(CallbackTask task, Executor executor) { 289 executor.execute(() -> { 290 if (mMyBindingId != mBgDataModel.lastBindId) { 291 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); 292 return; 293 } 294 Callbacks callbacks = mCallbacks.get(); 295 if (callbacks != null) { 296 task.execute(callbacks); 297 } 298 }); 299 } 300 newIdleLock(Object lock)301 public LooperIdleLock newIdleLock(Object lock) { 302 LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper()); 303 // If we are not binding, there is no reason to wait for idle. 304 if (mCallbacks.get() == null) { 305 idleLock.queueIdle(); 306 } 307 return idleLock; 308 } 309 } 310