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