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