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