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