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