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