1 /* 2 * Copyright (C) 2008 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; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; 20 21 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; 22 import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP; 23 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; 24 import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME; 25 import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE; 26 import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE; 27 import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE; 28 import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing; 29 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 30 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 31 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.PackageInstaller; 36 import android.content.pm.ShortcutInfo; 37 import android.os.UserHandle; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.util.Pair; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 import androidx.annotation.WorkerThread; 45 46 import com.android.launcher3.celllayout.CellPosMapper; 47 import com.android.launcher3.config.FeatureFlags; 48 import com.android.launcher3.icons.IconCache; 49 import com.android.launcher3.model.AddWorkspaceItemsTask; 50 import com.android.launcher3.model.AllAppsList; 51 import com.android.launcher3.model.BaseLauncherBinder; 52 import com.android.launcher3.model.BgDataModel; 53 import com.android.launcher3.model.BgDataModel.Callbacks; 54 import com.android.launcher3.model.CacheDataUpdatedTask; 55 import com.android.launcher3.model.ItemInstallQueue; 56 import com.android.launcher3.model.LoaderTask; 57 import com.android.launcher3.model.ModelDbController; 58 import com.android.launcher3.model.ModelDelegate; 59 import com.android.launcher3.model.ModelLauncherCallbacks; 60 import com.android.launcher3.model.ModelTaskController; 61 import com.android.launcher3.model.ModelWriter; 62 import com.android.launcher3.model.PackageInstallStateChangedTask; 63 import com.android.launcher3.model.PackageUpdatedTask; 64 import com.android.launcher3.model.ReloadStringCacheTask; 65 import com.android.launcher3.model.ShortcutsChangedTask; 66 import com.android.launcher3.model.UserLockStateChangedTask; 67 import com.android.launcher3.model.data.AppInfo; 68 import com.android.launcher3.model.data.ItemInfo; 69 import com.android.launcher3.model.data.WorkspaceItemInfo; 70 import com.android.launcher3.pm.InstallSessionTracker; 71 import com.android.launcher3.pm.PackageInstallInfo; 72 import com.android.launcher3.pm.UserCache; 73 import com.android.launcher3.shortcuts.ShortcutRequest; 74 import com.android.launcher3.util.IntSet; 75 import com.android.launcher3.util.ItemInfoMatcher; 76 import com.android.launcher3.util.PackageManagerHelper; 77 import com.android.launcher3.util.PackageUserKey; 78 import com.android.launcher3.util.Preconditions; 79 80 import java.io.FileDescriptor; 81 import java.io.PrintWriter; 82 import java.util.ArrayList; 83 import java.util.HashSet; 84 import java.util.List; 85 import java.util.concurrent.CancellationException; 86 import java.util.function.Consumer; 87 import java.util.function.Supplier; 88 89 /** 90 * Maintains in-memory state of the Launcher. It is expected that there should be only one 91 * LauncherModel object held in a static. Also provide APIs for updating the database state 92 * for the Launcher. 93 */ 94 public class LauncherModel implements InstallSessionTracker.Callback { 95 private static final boolean DEBUG_RECEIVER = false; 96 97 static final String TAG = "Launcher.Model"; 98 99 @NonNull 100 private final LauncherAppState mApp; 101 @NonNull 102 private final PackageManagerHelper mPmHelper; 103 @NonNull 104 private final ModelDbController mModelDbController; 105 @NonNull 106 private final Object mLock = new Object(); 107 @Nullable 108 private LoaderTask mLoaderTask; 109 private boolean mIsLoaderTaskRunning; 110 111 // only allow this once per reboot to reload work apps 112 private boolean mShouldReloadWorkProfile = true; 113 114 // Indicates whether the current model data is valid or not. 115 // We start off with everything not loaded. After that, we assume that 116 // our monitoring of the package manager provides all updates and we never 117 // need to do a requery. This is only ever touched from the loader thread. 118 private boolean mModelLoaded; 119 private boolean mModelDestroyed = false; isModelLoaded()120 public boolean isModelLoaded() { 121 synchronized (mLock) { 122 return mModelLoaded && mLoaderTask == null && !mModelDestroyed; 123 } 124 } 125 126 @NonNull 127 private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1); 128 129 // < only access in worker thread > 130 @NonNull 131 private final AllAppsList mBgAllAppsList; 132 133 /** 134 * All the static data should be accessed on the background thread, A lock should be acquired 135 * on this object when accessing any data from this model. 136 */ 137 @NonNull 138 private final BgDataModel mBgDataModel = new BgDataModel(); 139 140 @NonNull 141 private final ModelDelegate mModelDelegate; 142 143 private int mLastLoadId = -1; 144 145 // Runnable to check if the shortcuts permission has changed. 146 @NonNull 147 private final Runnable mDataValidationCheck = new Runnable() { 148 @Override 149 public void run() { 150 if (mModelLoaded) { 151 mModelDelegate.validateData(); 152 } 153 } 154 }; 155 LauncherModel(@onNull final Context context, @NonNull final LauncherAppState app, @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance)156 LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app, 157 @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, 158 @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) { 159 mApp = app; 160 mPmHelper = pmHelper; 161 mModelDbController = new ModelDbController(context); 162 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 163 mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList, 164 mBgDataModel, isPrimaryInstance); 165 } 166 167 @NonNull getModelDelegate()168 public ModelDelegate getModelDelegate() { 169 return mModelDelegate; 170 } 171 getModelDbController()172 public ModelDbController getModelDbController() { 173 return mModelDbController; 174 } 175 newModelCallbacks()176 public ModelLauncherCallbacks newModelCallbacks() { 177 return new ModelLauncherCallbacks(this::enqueueModelUpdateTask); 178 } 179 180 /** 181 * Adds the provided items to the workspace. 182 */ addAndBindAddedWorkspaceItems( @onNull final List<Pair<ItemInfo, Object>> itemList)183 public void addAndBindAddedWorkspaceItems( 184 @NonNull final List<Pair<ItemInfo, Object>> itemList) { 185 for (Callbacks cb : getCallbacks()) { 186 cb.preAddApps(); 187 } 188 enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); 189 } 190 191 @NonNull getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper, @Nullable final Callbacks owner)192 public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper, 193 @Nullable final Callbacks owner) { 194 return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper, 195 owner); 196 } 197 198 /** 199 * Called when the icon for an app changes, outside of package event 200 */ 201 @WorkerThread onAppIconChanged(@onNull final String packageName, @NonNull final UserHandle user)202 public void onAppIconChanged(@NonNull final String packageName, 203 @NonNull final UserHandle user) { 204 // Update the icon for the calendar package 205 Context context = mApp.getContext(); 206 enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName)); 207 208 List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user) 209 .forPackage(packageName).query(ShortcutRequest.PINNED); 210 if (!pinnedShortcuts.isEmpty()) { 211 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user, 212 false)); 213 } 214 } 215 216 /** 217 * Called when the workspace items have drastically changed 218 */ onWorkspaceUiChanged()219 public void onWorkspaceUiChanged() { 220 MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete); 221 } 222 223 /** 224 * Called when the model is destroyed 225 */ destroy()226 public void destroy() { 227 mModelDestroyed = true; 228 MODEL_EXECUTOR.execute(mModelDelegate::destroy); 229 } 230 onBroadcastIntent(@onNull final Intent intent)231 public void onBroadcastIntent(@NonNull final Intent intent) { 232 if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent); 233 final String action = intent.getAction(); 234 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 235 // If we have changed locale we need to clear out the labels in all apps/workspace. 236 forceReload(); 237 } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) { 238 enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate)); 239 } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) { 240 for (Callbacks cb : getCallbacks()) { 241 if (cb instanceof Launcher) { 242 ((Launcher) cb).recreate(); 243 } 244 } 245 } 246 } 247 248 /** 249 * Called then there use a user event 250 * @see UserCache#addUserEventListener 251 */ onUserEvent(UserHandle user, String action)252 public void onUserEvent(UserHandle user, String action) { 253 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) 254 && mShouldReloadWorkProfile) { 255 mShouldReloadWorkProfile = false; 256 forceReload(); 257 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) 258 || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { 259 mShouldReloadWorkProfile = false; 260 enqueueModelUpdateTask(new PackageUpdatedTask( 261 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); 262 } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action) 263 || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) { 264 enqueueModelUpdateTask(new UserLockStateChangedTask( 265 user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action))); 266 } else if (UserCache.ACTION_PROFILE_ADDED.equals(action) 267 || UserCache.ACTION_PROFILE_REMOVED.equals(action)) { 268 forceReload(); 269 } else if (ACTION_PROFILE_AVAILABLE.equals(action) 270 || ACTION_PROFILE_UNAVAILABLE.equals(action)) { 271 /* 272 * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set. 273 * For Work-profile this broadcast will be sent in addition to 274 * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. 275 * So effectively, this if block only handles the non-work profile case. 276 */ 277 enqueueModelUpdateTask(new PackageUpdatedTask( 278 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); 279 } 280 if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 281 LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0); 282 } 283 } 284 285 /** 286 * Reloads the workspace items from the DB and re-binds the workspace. This should generally 287 * not be called as DB updates are automatically followed by UI update 288 */ forceReload()289 public void forceReload() { 290 synchronized (mLock) { 291 // Stop any existing loaders first, so they don't set mModelLoaded to true later 292 stopLoader(); 293 mModelLoaded = false; 294 } 295 296 // Start the loader if launcher is already running, otherwise the loader will run, 297 // the next time launcher starts 298 if (hasCallbacks()) { 299 startLoader(); 300 } 301 } 302 303 /** 304 * Rebinds all existing callbacks with already loaded model 305 */ rebindCallbacks()306 public void rebindCallbacks() { 307 if (hasCallbacks()) { 308 startLoader(); 309 } 310 } 311 312 /** 313 * Removes an existing callback 314 */ removeCallbacks(@onNull final Callbacks callbacks)315 public void removeCallbacks(@NonNull final Callbacks callbacks) { 316 synchronized (mCallbacksList) { 317 Preconditions.assertUIThread(); 318 if (mCallbacksList.remove(callbacks)) { 319 if (stopLoader()) { 320 // Rebind existing callbacks 321 startLoader(); 322 } 323 } 324 } 325 } 326 327 /** 328 * Adds a callbacks to receive model updates 329 * @return true if workspace load was performed synchronously 330 */ addCallbacksAndLoad(@onNull final Callbacks callbacks)331 public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) { 332 synchronized (mLock) { 333 addCallbacks(callbacks); 334 return startLoader(new Callbacks[] { callbacks }); 335 336 } 337 } 338 339 /** 340 * Adds a callbacks to receive model updates 341 */ addCallbacks(@onNull final Callbacks callbacks)342 public void addCallbacks(@NonNull final Callbacks callbacks) { 343 Preconditions.assertUIThread(); 344 synchronized (mCallbacksList) { 345 mCallbacksList.add(callbacks); 346 } 347 } 348 349 /** 350 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 351 * @return true if the page could be bound synchronously. 352 */ startLoader()353 public boolean startLoader() { 354 return startLoader(new Callbacks[0]); 355 } 356 startLoader(@onNull final Callbacks[] newCallbacks)357 private boolean startLoader(@NonNull final Callbacks[] newCallbacks) { 358 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 359 ItemInstallQueue.INSTANCE.get(mApp.getContext()) 360 .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING); 361 synchronized (mLock) { 362 // If there is already one running, tell it to stop. 363 boolean wasRunning = stopLoader(); 364 boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning; 365 boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0; 366 final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks; 367 368 if (callbacksList.length > 0) { 369 // Clear any pending bind-runnables from the synchronized load process. 370 for (Callbacks cb : callbacksList) { 371 MAIN_EXECUTOR.execute(cb::clearPendingBinds); 372 } 373 374 BaseLauncherBinder launcherBinder = new BaseLauncherBinder( 375 mApp, mBgDataModel, mBgAllAppsList, callbacksList); 376 if (bindDirectly) { 377 // Divide the set of loaded items into those that we are binding synchronously, 378 // and everything else that is to be bound normally (asynchronously). 379 launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true); 380 // For now, continue posting the binding of AllApps as there are other 381 // issues that arise from that. 382 launcherBinder.bindAllApps(); 383 launcherBinder.bindDeepShortcuts(); 384 launcherBinder.bindWidgets(); 385 if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 386 mModelDelegate.bindAllModelExtras(callbacksList); 387 } 388 return true; 389 } else { 390 stopLoader(); 391 mLoaderTask = new LoaderTask( 392 mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder); 393 394 // Always post the loader task, instead of running directly 395 // (even on same thread) so that we exit any nested synchronized blocks 396 MODEL_EXECUTOR.post(mLoaderTask); 397 } 398 } 399 } 400 return false; 401 } 402 403 /** 404 * If there is already a loader task running, tell it to stop. 405 * @return true if an existing loader was stopped. 406 */ stopLoader()407 private boolean stopLoader() { 408 synchronized (mLock) { 409 LoaderTask oldTask = mLoaderTask; 410 mLoaderTask = null; 411 if (oldTask != null) { 412 oldTask.stopLocked(); 413 return true; 414 } 415 return false; 416 } 417 } 418 419 /** 420 * Loads the model if not loaded 421 * @param callback called with the data model upon successful load or null on model thread. 422 */ loadAsync(@onNull final Consumer<BgDataModel> callback)423 public void loadAsync(@NonNull final Consumer<BgDataModel> callback) { 424 synchronized (mLock) { 425 if (!mModelLoaded && !mIsLoaderTaskRunning) { 426 startLoader(); 427 } 428 } 429 MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null)); 430 } 431 432 @Override onInstallSessionCreated(@onNull final PackageInstallInfo sessionInfo)433 public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) { 434 if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { 435 enqueueModelUpdateTask((taskController, dataModel, apps) -> { 436 apps.addPromiseApp(mApp.getContext(), sessionInfo); 437 taskController.bindApplicationsIfNeeded(); 438 }); 439 } 440 } 441 442 @Override onSessionFailure(@onNull final String packageName, @NonNull final UserHandle user)443 public void onSessionFailure(@NonNull final String packageName, 444 @NonNull final UserHandle user) { 445 enqueueModelUpdateTask((taskController, dataModel, apps) -> { 446 IconCache iconCache = mApp.getIconCache(); 447 final IntSet removedIds = new IntSet(); 448 HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>(); 449 boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext()) 450 .isAppArchivedForUser(packageName, user); 451 synchronized (dataModel) { 452 if (isAppArchived) { 453 // Remove package icon cache entry for archived app in case of a session 454 // failure. 455 mApp.getIconCache().remove( 456 new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), 457 user); 458 } 459 460 for (ItemInfo info : dataModel.itemsIdMap) { 461 if (info instanceof WorkspaceItemInfo 462 && ((WorkspaceItemInfo) info).hasPromiseIconUi() 463 && user.equals(info.user) 464 && info.getIntent() != null) { 465 if (TextUtils.equals(packageName, info.getIntent().getPackage())) { 466 removedIds.add(info.id); 467 } 468 if (((WorkspaceItemInfo) info).isArchived()) { 469 WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info; 470 // Refresh icons on the workspace for archived apps. 471 iconCache.getTitleAndIcon(workspaceItem, 472 workspaceItem.usingLowResIcon()); 473 archivedWorkspaceItemsToCacheRefresh.add(workspaceItem); 474 } 475 } 476 } 477 478 if (isAppArchived) { 479 apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user); 480 } 481 } 482 483 if (!removedIds.isEmpty() && !isAppArchived) { 484 taskController.deleteAndBindComponentsRemoved( 485 ItemInfoMatcher.ofItemIds(removedIds), 486 "removed because install session failed"); 487 } 488 if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) { 489 taskController.bindUpdatedWorkspaceItems( 490 archivedWorkspaceItemsToCacheRefresh.stream().toList()); 491 } 492 if (isAppArchived) { 493 taskController.bindApplicationsIfNeeded(); 494 } 495 }); 496 } 497 498 @Override onPackageStateChanged(@onNull final PackageInstallInfo installInfo)499 public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) { 500 enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); 501 } 502 503 /** 504 * Updates the icons and label of all pending icons for the provided package name. 505 */ 506 @Override onUpdateSessionDisplay(@onNull final PackageUserKey key, @NonNull final PackageInstaller.SessionInfo info)507 public void onUpdateSessionDisplay(@NonNull final PackageUserKey key, 508 @NonNull final PackageInstaller.SessionInfo info) { 509 mApp.getIconCache().updateSessionCache(key, info); 510 511 HashSet<String> packages = new HashSet<>(); 512 packages.add(key.mPackageName); 513 enqueueModelUpdateTask(new CacheDataUpdatedTask( 514 CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages)); 515 } 516 517 public class LoaderTransaction implements AutoCloseable { 518 519 @NonNull 520 private final LoaderTask mTask; 521 LoaderTransaction(@onNull final LoaderTask task)522 private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException { 523 synchronized (mLock) { 524 if (mLoaderTask != task) { 525 throw new CancellationException("Loader already stopped"); 526 } 527 mLastLoadId++; 528 mTask = task; 529 mIsLoaderTaskRunning = true; 530 mModelLoaded = false; 531 } 532 } 533 commit()534 public void commit() { 535 synchronized (mLock) { 536 // Everything loaded bind the data. 537 mModelLoaded = true; 538 } 539 } 540 541 @Override close()542 public void close() { 543 synchronized (mLock) { 544 // If we are still the last one to be scheduled, remove ourselves. 545 if (mLoaderTask == mTask) { 546 mLoaderTask = null; 547 } 548 mIsLoaderTaskRunning = false; 549 } 550 } 551 } 552 beginLoader(@onNull final LoaderTask task)553 public LoaderTransaction beginLoader(@NonNull final LoaderTask task) 554 throws CancellationException { 555 return new LoaderTransaction(task); 556 } 557 558 /** 559 * Refreshes the cached shortcuts if the shortcut permission has changed. 560 * Current implementation simply reloads the workspace, but it can be optimized to 561 * use partial updates similar to {@link UserCache} 562 */ validateModelDataOnResume()563 public void validateModelDataOnResume() { 564 MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck); 565 MODEL_EXECUTOR.post(mDataValidationCheck); 566 } 567 568 /** 569 * Called when the icons for packages have been updated in the icon cache. 570 */ onPackageIconsUpdated(@onNull final HashSet<String> updatedPackages, @NonNull final UserHandle user)571 public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages, 572 @NonNull final UserHandle user) { 573 // If any package icon has changed (app was updated while launcher was dead), 574 // update the corresponding shortcuts. 575 enqueueModelUpdateTask(new CacheDataUpdatedTask( 576 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); 577 } 578 579 /** 580 * Called when the labels for the widgets has updated in the icon cache. 581 */ onWidgetLabelsUpdated(@onNull final HashSet<String> updatedPackages, @NonNull final UserHandle user)582 public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages, 583 @NonNull final UserHandle user) { 584 enqueueModelUpdateTask((taskController, dataModel, apps) -> { 585 dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp); 586 taskController.bindUpdatedWidgets(dataModel); 587 }); 588 } 589 enqueueModelUpdateTask(@onNull final ModelUpdateTask task)590 public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) { 591 if (mModelDestroyed) { 592 return; 593 } 594 MODEL_EXECUTOR.execute(() -> { 595 if (!isModelLoaded()) { 596 // Loader has not yet run. 597 return; 598 } 599 ModelTaskController controller = new ModelTaskController( 600 mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR); 601 task.execute(controller, mBgDataModel, mBgAllAppsList); 602 }); 603 } 604 605 /** 606 * A task to be executed on the current callbacks on the UI thread. 607 * If there is no current callbacks, the task is ignored. 608 */ 609 public interface CallbackTask { 610 execute(@onNull Callbacks callbacks)611 void execute(@NonNull Callbacks callbacks); 612 } 613 614 public interface ModelUpdateTask { 615 execute(@onNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps)616 void execute(@NonNull ModelTaskController taskController, 617 @NonNull BgDataModel dataModel, @NonNull AllAppsList apps); 618 } 619 updateAndBindWorkspaceItem(@onNull final WorkspaceItemInfo si, @NonNull final ShortcutInfo info)620 public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si, 621 @NonNull final ShortcutInfo info) { 622 updateAndBindWorkspaceItem(() -> { 623 si.updateFromDeepShortcutInfo(info, mApp.getContext()); 624 mApp.getIconCache().getShortcutIcon(si, info); 625 return si; 626 }); 627 } 628 629 /** 630 * Utility method to update a shortcut on the background thread. 631 */ updateAndBindWorkspaceItem( @onNull final Supplier<WorkspaceItemInfo> itemProvider)632 public void updateAndBindWorkspaceItem( 633 @NonNull final Supplier<WorkspaceItemInfo> itemProvider) { 634 enqueueModelUpdateTask((taskController, dataModel, apps) -> { 635 WorkspaceItemInfo info = itemProvider.get(); 636 taskController.getModelWriter().updateItemInDatabase(info); 637 ArrayList<WorkspaceItemInfo> update = new ArrayList<>(); 638 update.add(info); 639 taskController.bindUpdatedWorkspaceItems(update); 640 }); 641 } 642 refreshAndBindWidgetsAndShortcuts(@ullable final PackageUserKey packageUser)643 public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) { 644 enqueueModelUpdateTask((taskController, dataModel, apps) -> { 645 dataModel.widgetsModel.update(taskController.getApp(), packageUser); 646 taskController.bindUpdatedWidgets(dataModel); 647 }); 648 } 649 dumpState(@ullable final String prefix, @Nullable final FileDescriptor fd, @NonNull final PrintWriter writer, @NonNull final String[] args)650 public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd, 651 @NonNull final PrintWriter writer, @NonNull final String[] args) { 652 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 653 writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); 654 for (AppInfo info : mBgAllAppsList.data) { 655 writer.println(prefix + " title=\"" + info.title 656 + "\" bitmapIcon=" + info.bitmap.icon 657 + " componentName=" + info.componentName.getPackageName()); 658 } 659 writer.println(); 660 } 661 mModelDelegate.dump(prefix, fd, writer, args); 662 mBgDataModel.dump(prefix, fd, writer, args); 663 } 664 665 /** 666 * Returns true if there are any callbacks attached to the model 667 */ hasCallbacks()668 public boolean hasCallbacks() { 669 synchronized (mCallbacksList) { 670 return !mCallbacksList.isEmpty(); 671 } 672 } 673 674 /** 675 * Returns an array of currently attached callbacks 676 */ 677 @NonNull getCallbacks()678 public Callbacks[] getCallbacks() { 679 synchronized (mCallbacksList) { 680 return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]); 681 } 682 } 683 684 /** 685 * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the 686 * transaction should be ignored. 687 */ getLastLoadId()688 public int getLastLoadId() { 689 return mLastLoadId; 690 } 691 } 692