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