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_DOGFOOD_BUILD; 21 22 import android.content.BroadcastReceiver; 23 import android.content.ContentProviderOperation; 24 import android.content.ContentResolver; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.Looper; 32 import android.os.Process; 33 import android.os.UserHandle; 34 import android.support.annotation.Nullable; 35 import android.text.TextUtils; 36 import android.util.Log; 37 import android.util.Pair; 38 39 import com.android.launcher3.compat.LauncherAppsCompat; 40 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 41 import com.android.launcher3.compat.UserManagerCompat; 42 import com.android.launcher3.graphics.LauncherIcons; 43 import com.android.launcher3.model.AddWorkspaceItemsTask; 44 import com.android.launcher3.model.BaseModelUpdateTask; 45 import com.android.launcher3.model.BgDataModel; 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.provider.LauncherDbUtils; 55 import com.android.launcher3.shortcuts.DeepShortcutManager; 56 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 57 import com.android.launcher3.util.ComponentKey; 58 import com.android.launcher3.util.ItemInfoMatcher; 59 import com.android.launcher3.util.MultiHashMap; 60 import com.android.launcher3.util.PackageUserKey; 61 import com.android.launcher3.util.Preconditions; 62 import com.android.launcher3.util.Provider; 63 import com.android.launcher3.util.Thunk; 64 import com.android.launcher3.util.ViewOnDrawExecutor; 65 import com.android.launcher3.widget.WidgetListRowEntry; 66 67 import java.io.FileDescriptor; 68 import java.io.PrintWriter; 69 import java.lang.ref.WeakReference; 70 import java.util.ArrayList; 71 import java.util.HashSet; 72 import java.util.Iterator; 73 import java.util.List; 74 import java.util.concurrent.CancellationException; 75 import java.util.concurrent.Executor; 76 77 /** 78 * Maintains in-memory state of the Launcher. It is expected that there should be only one 79 * LauncherModel object held in a static. Also provide APIs for updating the database state 80 * for the Launcher. 81 */ 82 public class LauncherModel extends BroadcastReceiver 83 implements LauncherAppsCompat.OnAppsChangedCallbackCompat { 84 private static final boolean DEBUG_RECEIVER = false; 85 86 static final String TAG = "Launcher.Model"; 87 88 private final MainThreadExecutor mUiExecutor = new MainThreadExecutor(); 89 @Thunk final LauncherAppState mApp; 90 @Thunk final Object mLock = new Object(); 91 @Thunk 92 LoaderTask mLoaderTask; 93 @Thunk boolean mIsLoaderTaskRunning; 94 95 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 96 static { 97 sWorkerThread.start(); 98 } 99 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 100 101 // Indicates whether the current model data is valid or not. 102 // We start off with everything not loaded. After that, we assume that 103 // our monitoring of the package manager provides all updates and we never 104 // need to do a requery. This is only ever touched from the loader thread. 105 private boolean mModelLoaded; 106 public boolean isModelLoaded() { 107 synchronized (mLock) { 108 return mModelLoaded && mLoaderTask == null; 109 } 110 } 111 112 @Thunk WeakReference<Callbacks> mCallbacks; 113 114 // < only access in worker thread > 115 private final AllAppsList mBgAllAppsList; 116 117 /** 118 * All the static data should be accessed on the background thread, A lock should be acquired 119 * on this object when accessing any data from this model. 120 */ 121 static final BgDataModel sBgDataModel = new BgDataModel(); 122 123 // Runnable to check if the shortcuts permission has changed. 124 private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { 125 @Override 126 public void run() { 127 if (mModelLoaded) { 128 boolean hasShortcutHostPermission = 129 DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission(); 130 if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) { 131 forceReload(); 132 } 133 } 134 } 135 }; 136 137 public interface Callbacks { 138 public void rebindModel(); 139 140 public int getCurrentWorkspaceScreen(); 141 public void clearPendingBinds(); 142 public void startBinding(); 143 public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); 144 public void bindScreens(ArrayList<Long> orderedScreenIds); 145 public void finishFirstPageBind(ViewOnDrawExecutor executor); 146 public void finishBindingItems(); 147 public void bindAllApplications(ArrayList<AppInfo> apps); 148 public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps); 149 public void bindAppsAdded(ArrayList<Long> newScreens, 150 ArrayList<ItemInfo> addNotAnimated, 151 ArrayList<ItemInfo> addAnimated); 152 public void bindPromiseAppProgressUpdated(PromiseAppInfo app); 153 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user); 154 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); 155 public void bindRestoreItemsChange(HashSet<ItemInfo> updates); 156 public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); 157 public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); 158 public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); 159 public void onPageBoundSynchronously(int page); 160 public void executeOnNextDraw(ViewOnDrawExecutor executor); 161 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); 162 } 163 164 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { 165 mApp = app; 166 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 167 } 168 169 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 170 * posted on the worker thread handler. */ 171 private static void runOnWorkerThread(Runnable r) { 172 if (sWorkerThread.getThreadId() == Process.myTid()) { 173 r.run(); 174 } else { 175 // If we are not on the worker thread, then post to the worker handler 176 sWorker.post(r); 177 } 178 } 179 180 public void setPackageState(PackageInstallInfo installInfo) { 181 enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); 182 } 183 184 /** 185 * Updates the icons and label of all pending icons for the provided package name. 186 */ 187 public void updateSessionDisplayInfo(final String packageName) { 188 HashSet<String> packages = new HashSet<>(); 189 packages.add(packageName); 190 enqueueModelUpdateTask(new CacheDataUpdatedTask( 191 CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); 192 } 193 194 /** 195 * Adds the provided items to the workspace. 196 */ 197 public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) { 198 enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList)); 199 } 200 201 public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) { 202 return new ModelWriter(mApp.getContext(), this, sBgDataModel, 203 hasVerticalHotseat, verifyChanges); 204 } 205 206 static void checkItemInfoLocked( 207 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { 208 ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); 209 if (modelItem != null && item != modelItem) { 210 // check all the data is consistent 211 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { 212 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; 213 ShortcutInfo shortcut = (ShortcutInfo) item; 214 if (modelShortcut.title.toString().equals(shortcut.title.toString()) && 215 modelShortcut.intent.filterEquals(shortcut.intent) && 216 modelShortcut.id == shortcut.id && 217 modelShortcut.itemType == shortcut.itemType && 218 modelShortcut.container == shortcut.container && 219 modelShortcut.screenId == shortcut.screenId && 220 modelShortcut.cellX == shortcut.cellX && 221 modelShortcut.cellY == shortcut.cellY && 222 modelShortcut.spanX == shortcut.spanX && 223 modelShortcut.spanY == shortcut.spanY) { 224 // For all intents and purposes, this is the same object 225 return; 226 } 227 } 228 229 // the modelItem needs to match up perfectly with item if our model is 230 // to be consistent with the database-- for now, just require 231 // modelItem == item or the equality check above 232 String msg = "item: " + ((item != null) ? item.toString() : "null") + 233 "modelItem: " + 234 ((modelItem != null) ? modelItem.toString() : "null") + 235 "Error: ItemInfo passed to checkItemInfo doesn't match original"; 236 RuntimeException e = new RuntimeException(msg); 237 if (stackTrace != null) { 238 e.setStackTrace(stackTrace); 239 } 240 throw e; 241 } 242 } 243 244 static void checkItemInfo(final ItemInfo item) { 245 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 246 final long itemId = item.id; 247 Runnable r = new Runnable() { 248 public void run() { 249 synchronized (sBgDataModel) { 250 checkItemInfoLocked(itemId, item, stackTrace); 251 } 252 } 253 }; 254 runOnWorkerThread(r); 255 } 256 257 /** 258 * Update the order of the workspace screens in the database. The array list contains 259 * a list of screen ids in the order that they should appear. 260 */ 261 public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { 262 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); 263 final ContentResolver cr = context.getContentResolver(); 264 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 265 266 // Remove any negative screen ids -- these aren't persisted 267 Iterator<Long> iter = screensCopy.iterator(); 268 while (iter.hasNext()) { 269 long id = iter.next(); 270 if (id < 0) { 271 iter.remove(); 272 } 273 } 274 275 Runnable r = new Runnable() { 276 @Override 277 public void run() { 278 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 279 // Clear the table 280 ops.add(ContentProviderOperation.newDelete(uri).build()); 281 int count = screensCopy.size(); 282 for (int i = 0; i < count; i++) { 283 ContentValues v = new ContentValues(); 284 long screenId = screensCopy.get(i); 285 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 286 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 287 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); 288 } 289 290 try { 291 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 292 } catch (Exception ex) { 293 throw new RuntimeException(ex); 294 } 295 296 synchronized (sBgDataModel) { 297 sBgDataModel.workspaceScreens.clear(); 298 sBgDataModel.workspaceScreens.addAll(screensCopy); 299 } 300 } 301 }; 302 runOnWorkerThread(r); 303 } 304 305 /** 306 * Set this as the current Launcher activity object for the loader. 307 */ 308 public void initialize(Callbacks callbacks) { 309 synchronized (mLock) { 310 Preconditions.assertUIThread(); 311 mCallbacks = new WeakReference<>(callbacks); 312 } 313 } 314 315 @Override 316 public void onPackageChanged(String packageName, UserHandle user) { 317 int op = PackageUpdatedTask.OP_UPDATE; 318 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 319 } 320 321 @Override 322 public void onPackageRemoved(String packageName, UserHandle user) { 323 onPackagesRemoved(user, packageName); 324 } 325 326 public void onPackagesRemoved(UserHandle user, String... packages) { 327 int op = PackageUpdatedTask.OP_REMOVE; 328 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); 329 } 330 331 @Override 332 public void onPackageAdded(String packageName, UserHandle user) { 333 int op = PackageUpdatedTask.OP_ADD; 334 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 335 } 336 337 @Override 338 public void onPackagesAvailable(String[] packageNames, UserHandle user, 339 boolean replacing) { 340 enqueueModelUpdateTask( 341 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); 342 } 343 344 @Override 345 public void onPackagesUnavailable(String[] packageNames, UserHandle user, 346 boolean replacing) { 347 if (!replacing) { 348 enqueueModelUpdateTask(new PackageUpdatedTask( 349 PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); 350 } 351 } 352 353 @Override 354 public void onPackagesSuspended(String[] packageNames, UserHandle user) { 355 enqueueModelUpdateTask(new PackageUpdatedTask( 356 PackageUpdatedTask.OP_SUSPEND, user, packageNames)); 357 } 358 359 @Override 360 public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { 361 enqueueModelUpdateTask(new PackageUpdatedTask( 362 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); 363 } 364 365 @Override 366 public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, 367 UserHandle user) { 368 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); 369 } 370 371 public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, 372 UserHandle user) { 373 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); 374 } 375 376 /** 377 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 378 * ACTION_PACKAGE_CHANGED. 379 */ 380 @Override 381 public void onReceive(Context context, Intent intent) { 382 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); 383 384 final String action = intent.getAction(); 385 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 386 // If we have changed locale we need to clear out the labels in all apps/workspace. 387 forceReload(); 388 } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) 389 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 390 UserManagerCompat.getInstance(context).enableAndResetCache(); 391 forceReload(); 392 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 393 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 394 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 395 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); 396 if (user != null) { 397 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 398 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { 399 enqueueModelUpdateTask(new PackageUpdatedTask( 400 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); 401 } 402 403 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so 404 // we need to run the state change task again. 405 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 406 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 407 enqueueModelUpdateTask(new UserLockStateChangedTask(user)); 408 } 409 } 410 } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) { 411 forceReload(); 412 } 413 } 414 415 /** 416 * Reloads the workspace items from the DB and re-binds the workspace. This should generally 417 * not be called as DB updates are automatically followed by UI update 418 */ 419 public void forceReload() { 420 synchronized (mLock) { 421 // Stop any existing loaders first, so they don't set mModelLoaded to true later 422 stopLoader(); 423 mModelLoaded = false; 424 } 425 426 // Start the loader if launcher is already running, otherwise the loader will run, 427 // the next time launcher starts 428 Callbacks callbacks = getCallback(); 429 if (callbacks != null) { 430 startLoader(callbacks.getCurrentWorkspaceScreen()); 431 } 432 } 433 434 public boolean isCurrentCallbacks(Callbacks callbacks) { 435 return (mCallbacks != null && mCallbacks.get() == callbacks); 436 } 437 438 /** 439 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 440 * @return true if the page could be bound synchronously. 441 */ 442 public boolean startLoader(int synchronousBindPage) { 443 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 444 InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING); 445 synchronized (mLock) { 446 // Don't bother to start the thread if we know it's not going to do anything 447 if (mCallbacks != null && mCallbacks.get() != null) { 448 final Callbacks oldCallbacks = mCallbacks.get(); 449 // Clear any pending bind-runnables from the synchronized load process. 450 mUiExecutor.execute(oldCallbacks::clearPendingBinds); 451 452 // If there is already one running, tell it to stop. 453 stopLoader(); 454 LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel, 455 mBgAllAppsList, synchronousBindPage, mCallbacks); 456 if (mModelLoaded && !mIsLoaderTaskRunning) { 457 // Divide the set of loaded items into those that we are binding synchronously, 458 // and everything else that is to be bound normally (asynchronously). 459 loaderResults.bindWorkspace(); 460 // For now, continue posting the binding of AllApps as there are other 461 // issues that arise from that. 462 loaderResults.bindAllApps(); 463 loaderResults.bindDeepShortcuts(); 464 loaderResults.bindWidgets(); 465 return true; 466 } else { 467 startLoaderForResults(loaderResults); 468 } 469 } 470 } 471 return false; 472 } 473 474 /** 475 * If there is already a loader task running, tell it to stop. 476 */ 477 public void stopLoader() { 478 synchronized (mLock) { 479 LoaderTask oldTask = mLoaderTask; 480 mLoaderTask = null; 481 if (oldTask != null) { 482 oldTask.stopLocked(); 483 } 484 } 485 } 486 487 public void startLoaderForResults(LoaderResults results) { 488 synchronized (mLock) { 489 stopLoader(); 490 mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results); 491 runOnWorkerThread(mLoaderTask); 492 } 493 } 494 495 public void startLoaderForResultsIfNotLoaded(LoaderResults results) { 496 synchronized (mLock) { 497 if (!isModelLoaded()) { 498 Log.d(TAG, "Workspace not loaded, loading now"); 499 startLoaderForResults(results); 500 } 501 } 502 } 503 504 /** 505 * Loads the workspace screen ids in an ordered list. 506 */ 507 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) { 508 final ContentResolver contentResolver = context.getContentResolver(); 509 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 510 511 // Get screens ordered by rank. 512 return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query( 513 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); 514 } 515 516 public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) { 517 enqueueModelUpdateTask(new BaseModelUpdateTask() { 518 @Override 519 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 520 apps.addPromiseApp(app.getContext(), sessionInfo); 521 if (!apps.added.isEmpty()) { 522 final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added); 523 apps.added.clear(); 524 scheduleCallbackTask(new CallbackTask() { 525 @Override 526 public void execute(Callbacks callbacks) { 527 callbacks.bindAppsAddedOrUpdated(arrayList); 528 } 529 }); 530 } 531 } 532 }); 533 } 534 535 public class LoaderTransaction implements AutoCloseable { 536 537 private final LoaderTask mTask; 538 539 private LoaderTransaction(LoaderTask task) throws CancellationException { 540 synchronized (mLock) { 541 if (mLoaderTask != task) { 542 throw new CancellationException("Loader already stopped"); 543 } 544 mTask = task; 545 mIsLoaderTaskRunning = true; 546 mModelLoaded = false; 547 } 548 } 549 550 public void commit() { 551 synchronized (mLock) { 552 // Everything loaded bind the data. 553 mModelLoaded = true; 554 } 555 } 556 557 @Override 558 public void close() { 559 synchronized (mLock) { 560 // If we are still the last one to be scheduled, remove ourselves. 561 if (mLoaderTask == mTask) { 562 mLoaderTask = null; 563 } 564 mIsLoaderTaskRunning = false; 565 } 566 } 567 } 568 569 public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException { 570 return new LoaderTransaction(task); 571 } 572 573 /** 574 * Refreshes the cached shortcuts if the shortcut permission has changed. 575 * Current implementation simply reloads the workspace, but it can be optimized to 576 * use partial updates similar to {@link UserManagerCompat} 577 */ 578 public void refreshShortcutsIfRequired() { 579 if (Utilities.ATLEAST_NOUGAT_MR1) { 580 sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); 581 sWorker.post(mShortcutPermissionCheckRunnable); 582 } 583 } 584 585 /** 586 * Called when the icons for packages have been updated in the icon cache. 587 */ 588 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) { 589 // If any package icon has changed (app was updated while launcher was dead), 590 // update the corresponding shortcuts. 591 enqueueModelUpdateTask(new CacheDataUpdatedTask( 592 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); 593 } 594 595 public void enqueueModelUpdateTask(ModelUpdateTask task) { 596 task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor); 597 runOnWorkerThread(task); 598 } 599 600 /** 601 * A task to be executed on the current callbacks on the UI thread. 602 * If there is no current callbacks, the task is ignored. 603 */ 604 public interface CallbackTask { 605 606 void execute(Callbacks callbacks); 607 } 608 609 /** 610 * A runnable which changes/updates the data model of the launcher based on certain events. 611 */ 612 public interface ModelUpdateTask extends Runnable { 613 614 /** 615 * Called before the task is posted to initialize the internal state. 616 */ 617 void init(LauncherAppState app, LauncherModel model, 618 BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor); 619 620 } 621 622 public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) { 623 updateAndBindShortcutInfo(new Provider<ShortcutInfo>() { 624 @Override 625 public ShortcutInfo get() { 626 si.updateFromDeepShortcutInfo(info, mApp.getContext()); 627 LauncherIcons li = LauncherIcons.obtain(mApp.getContext()); 628 li.createShortcutIcon(info).applyTo(si); 629 li.recycle(); 630 return si; 631 } 632 }); 633 } 634 635 /** 636 * Utility method to update a shortcut on the background thread. 637 */ 638 public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) { 639 enqueueModelUpdateTask(new BaseModelUpdateTask() { 640 @Override 641 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 642 ShortcutInfo info = shortcutProvider.get(); 643 ArrayList<ShortcutInfo> update = new ArrayList<>(); 644 update.add(info); 645 bindUpdatedShortcuts(update, info.user); 646 } 647 }); 648 } 649 650 public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) { 651 enqueueModelUpdateTask(new BaseModelUpdateTask() { 652 @Override 653 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 654 dataModel.widgetsModel.update(app, packageUser); 655 bindUpdatedWidgets(dataModel); 656 } 657 }); 658 } 659 660 public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 661 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 662 writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); 663 for (AppInfo info : mBgAllAppsList.data) { 664 writer.println(prefix + " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap 665 + " componentName=" + info.componentName.getPackageName()); 666 } 667 } 668 sBgDataModel.dump(prefix, fd, writer, args); 669 } 670 671 public Callbacks getCallback() { 672 return mCallbacks != null ? mCallbacks.get() : null; 673 } 674 675 /** 676 * @return the looper for the worker thread which can be used to start background tasks. 677 */ 678 public static Looper getWorkerLooper() { 679 return sWorkerThread.getLooper(); 680 } 681 682 public static void setWorkerPriority(final int priority) { 683 Process.setThreadPriority(sWorkerThread.getThreadId(), priority); 684 } 685 } 686