LauncherModel.java revision 86222d23e28a09a7a26e7a0d4b4830ded2efb384
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 17package com.android.launcher3; 18 19import android.appwidget.AppWidgetProviderInfo; 20import android.content.BroadcastReceiver; 21import android.content.ComponentName; 22import android.content.ContentProviderOperation; 23import android.content.ContentResolver; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.LauncherActivityInfo; 29import android.net.Uri; 30import android.os.Handler; 31import android.os.HandlerThread; 32import android.os.Looper; 33import android.os.Process; 34import android.os.SystemClock; 35import android.os.Trace; 36import android.os.UserHandle; 37import android.support.annotation.Nullable; 38import android.text.TextUtils; 39import android.util.Log; 40import android.util.LongSparseArray; 41import android.util.MutableInt; 42 43import com.android.launcher3.compat.AppWidgetManagerCompat; 44import com.android.launcher3.compat.LauncherAppsCompat; 45import com.android.launcher3.compat.PackageInstallerCompat; 46import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 47import com.android.launcher3.compat.UserManagerCompat; 48import com.android.launcher3.config.ProviderConfig; 49import com.android.launcher3.dynamicui.ExtractionUtils; 50import com.android.launcher3.folder.Folder; 51import com.android.launcher3.folder.FolderIcon; 52import com.android.launcher3.graphics.LauncherIcons; 53import com.android.launcher3.logging.FileLog; 54import com.android.launcher3.model.AddWorkspaceItemsTask; 55import com.android.launcher3.model.BgDataModel; 56import com.android.launcher3.model.CacheDataUpdatedTask; 57import com.android.launcher3.model.ExtendedModelTask; 58import com.android.launcher3.model.GridSizeMigrationTask; 59import com.android.launcher3.model.LoaderCursor; 60import com.android.launcher3.model.ModelWriter; 61import com.android.launcher3.model.PackageInstallStateChangedTask; 62import com.android.launcher3.model.PackageItemInfo; 63import com.android.launcher3.model.PackageUpdatedTask; 64import com.android.launcher3.model.SdCardAvailableReceiver; 65import com.android.launcher3.model.ShortcutsChangedTask; 66import com.android.launcher3.model.UserLockStateChangedTask; 67import com.android.launcher3.model.WidgetItem; 68import com.android.launcher3.model.WidgetsModel; 69import com.android.launcher3.provider.ImportDataTask; 70import com.android.launcher3.provider.LauncherDbUtils; 71import com.android.launcher3.shortcuts.DeepShortcutManager; 72import com.android.launcher3.shortcuts.ShortcutInfoCompat; 73import com.android.launcher3.shortcuts.ShortcutKey; 74import com.android.launcher3.util.ComponentKey; 75import com.android.launcher3.util.ManagedProfileHeuristic; 76import com.android.launcher3.util.MultiHashMap; 77import com.android.launcher3.util.PackageManagerHelper; 78import com.android.launcher3.util.PackageUserKey; 79import com.android.launcher3.util.Preconditions; 80import com.android.launcher3.util.Provider; 81import com.android.launcher3.util.Thunk; 82import com.android.launcher3.util.ViewOnDrawExecutor; 83 84import java.io.FileDescriptor; 85import java.io.PrintWriter; 86import java.lang.ref.WeakReference; 87import java.util.ArrayList; 88import java.util.Collections; 89import java.util.Comparator; 90import java.util.HashMap; 91import java.util.HashSet; 92import java.util.Iterator; 93import java.util.List; 94import java.util.Map; 95import java.util.Set; 96import java.util.concurrent.CancellationException; 97import java.util.concurrent.Executor; 98 99/** 100 * Maintains in-memory state of the Launcher. It is expected that there should be only one 101 * LauncherModel object held in a static. Also provide APIs for updating the database state 102 * for the Launcher. 103 */ 104public class LauncherModel extends BroadcastReceiver 105 implements LauncherAppsCompat.OnAppsChangedCallbackCompat { 106 static final boolean DEBUG_LOADERS = false; 107 private static final boolean DEBUG_RECEIVER = false; 108 109 static final String TAG = "Launcher.Model"; 110 111 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 112 private static final long INVALID_SCREEN_ID = -1L; 113 114 @Thunk final LauncherAppState mApp; 115 @Thunk final Object mLock = new Object(); 116 @Thunk DeferredHandler mHandler = new DeferredHandler(); 117 @Thunk LoaderTask mLoaderTask; 118 @Thunk boolean mIsLoaderTaskRunning; 119 @Thunk boolean mHasLoaderCompletedOnce; 120 121 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 122 static { 123 sWorkerThread.start(); 124 } 125 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 126 127 // Indicates whether the current model data is valid or not. 128 // We start off with everything not loaded. After that, we assume that 129 // our monitoring of the package manager provides all updates and we never 130 // need to do a requery. This is only ever touched from the loader thread. 131 private boolean mModelLoaded; 132 133 /** 134 * Set of runnables to be called on the background thread after the workspace binding 135 * is complete. 136 */ 137 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>(); 138 139 @Thunk WeakReference<Callbacks> mCallbacks; 140 141 // < only access in worker thread > 142 private final AllAppsList mBgAllAppsList; 143 // Entire list of widgets. 144 private final WidgetsModel mBgWidgetsModel; 145 146 private boolean mHasShortcutHostPermission; 147 // Runnable to check if the shortcuts permission has changed. 148 private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { 149 @Override 150 public void run() { 151 if (mModelLoaded) { 152 boolean hasShortcutHostPermission = 153 DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission(); 154 if (hasShortcutHostPermission != mHasShortcutHostPermission) { 155 forceReload(); 156 } 157 } 158 } 159 }; 160 161 /** 162 * All the static data should be accessed on the background thread, A lock should be acquired 163 * on this object when accessing any data from this model. 164 */ 165 static final BgDataModel sBgDataModel = new BgDataModel(); 166 167 // </ only access in worker thread > 168 169 private final IconCache mIconCache; 170 171 private final LauncherAppsCompat mLauncherApps; 172 private final UserManagerCompat mUserManager; 173 174 public interface Callbacks { 175 public boolean setLoadOnResume(); 176 public int getCurrentWorkspaceScreen(); 177 public void clearPendingBinds(); 178 public void startBinding(); 179 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 180 boolean forceAnimateIcons); 181 public void bindScreens(ArrayList<Long> orderedScreenIds); 182 public void finishFirstPageBind(ViewOnDrawExecutor executor); 183 public void finishBindingItems(); 184 public void bindAppWidget(LauncherAppWidgetInfo info); 185 public void bindAllApplications(ArrayList<AppInfo> apps); 186 public void bindAppsAdded(ArrayList<Long> newScreens, 187 ArrayList<ItemInfo> addNotAnimated, 188 ArrayList<ItemInfo> addAnimated, 189 ArrayList<AppInfo> addedApps); 190 public void bindAppsUpdated(ArrayList<AppInfo> apps); 191 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, 192 ArrayList<ShortcutInfo> removed, UserHandle user); 193 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); 194 public void bindRestoreItemsChange(HashSet<ItemInfo> updates); 195 public void bindWorkspaceComponentsRemoved( 196 HashSet<String> packageNames, HashSet<ComponentName> components, 197 UserHandle user); 198 public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); 199 public void notifyWidgetProvidersChanged(); 200 public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets); 201 public void onPageBoundSynchronously(int page); 202 public void executeOnNextDraw(ViewOnDrawExecutor executor); 203 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); 204 } 205 206 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { 207 Context context = app.getContext(); 208 mApp = app; 209 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 210 mBgWidgetsModel = new WidgetsModel(iconCache, appFilter); 211 mIconCache = iconCache; 212 213 mLauncherApps = LauncherAppsCompat.getInstance(context); 214 mUserManager = UserManagerCompat.getInstance(context); 215 } 216 217 /** Runs the specified runnable immediately if called from the main thread, otherwise it is 218 * posted on the main thread handler. */ 219 private void runOnMainThread(Runnable r) { 220 if (sWorkerThread.getThreadId() == Process.myTid()) { 221 // If we are on the worker thread, post onto the main handler 222 mHandler.post(r); 223 } else { 224 r.run(); 225 } 226 } 227 228 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 229 * posted on the worker thread handler. */ 230 private static void runOnWorkerThread(Runnable r) { 231 if (sWorkerThread.getThreadId() == Process.myTid()) { 232 r.run(); 233 } else { 234 // If we are not on the worker thread, then post to the worker handler 235 sWorker.post(r); 236 } 237 } 238 239 public void setPackageState(PackageInstallInfo installInfo) { 240 enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); 241 } 242 243 /** 244 * Updates the icons and label of all pending icons for the provided package name. 245 */ 246 public void updateSessionDisplayInfo(final String packageName) { 247 HashSet<String> packages = new HashSet<>(); 248 packages.add(packageName); 249 enqueueModelUpdateTask(new CacheDataUpdatedTask( 250 CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); 251 } 252 253 /** 254 * Adds the provided items to the workspace. 255 */ 256 public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) { 257 addAndBindAddedWorkspaceItems(Provider.of(workspaceApps)); 258 } 259 260 /** 261 * Adds the provided items to the workspace. 262 */ 263 public void addAndBindAddedWorkspaceItems( 264 Provider<List<ItemInfo>> appsProvider) { 265 enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider)); 266 } 267 268 public ModelWriter getWriter(boolean hasVerticalHotseat) { 269 return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat); 270 } 271 272 static void checkItemInfoLocked( 273 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { 274 ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); 275 if (modelItem != null && item != modelItem) { 276 // check all the data is consistent 277 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { 278 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; 279 ShortcutInfo shortcut = (ShortcutInfo) item; 280 if (modelShortcut.title.toString().equals(shortcut.title.toString()) && 281 modelShortcut.intent.filterEquals(shortcut.intent) && 282 modelShortcut.id == shortcut.id && 283 modelShortcut.itemType == shortcut.itemType && 284 modelShortcut.container == shortcut.container && 285 modelShortcut.screenId == shortcut.screenId && 286 modelShortcut.cellX == shortcut.cellX && 287 modelShortcut.cellY == shortcut.cellY && 288 modelShortcut.spanX == shortcut.spanX && 289 modelShortcut.spanY == shortcut.spanY) { 290 // For all intents and purposes, this is the same object 291 return; 292 } 293 } 294 295 // the modelItem needs to match up perfectly with item if our model is 296 // to be consistent with the database-- for now, just require 297 // modelItem == item or the equality check above 298 String msg = "item: " + ((item != null) ? item.toString() : "null") + 299 "modelItem: " + 300 ((modelItem != null) ? modelItem.toString() : "null") + 301 "Error: ItemInfo passed to checkItemInfo doesn't match original"; 302 RuntimeException e = new RuntimeException(msg); 303 if (stackTrace != null) { 304 e.setStackTrace(stackTrace); 305 } 306 throw e; 307 } 308 } 309 310 static void checkItemInfo(final ItemInfo item) { 311 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 312 final long itemId = item.id; 313 Runnable r = new Runnable() { 314 public void run() { 315 synchronized (sBgDataModel) { 316 checkItemInfoLocked(itemId, item, stackTrace); 317 } 318 } 319 }; 320 runOnWorkerThread(r); 321 } 322 323 /** 324 * Update the order of the workspace screens in the database. The array list contains 325 * a list of screen ids in the order that they should appear. 326 */ 327 public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { 328 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); 329 final ContentResolver cr = context.getContentResolver(); 330 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 331 332 // Remove any negative screen ids -- these aren't persisted 333 Iterator<Long> iter = screensCopy.iterator(); 334 while (iter.hasNext()) { 335 long id = iter.next(); 336 if (id < 0) { 337 iter.remove(); 338 } 339 } 340 341 Runnable r = new Runnable() { 342 @Override 343 public void run() { 344 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 345 // Clear the table 346 ops.add(ContentProviderOperation.newDelete(uri).build()); 347 int count = screensCopy.size(); 348 for (int i = 0; i < count; i++) { 349 ContentValues v = new ContentValues(); 350 long screenId = screensCopy.get(i); 351 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 352 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 353 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); 354 } 355 356 try { 357 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 358 } catch (Exception ex) { 359 throw new RuntimeException(ex); 360 } 361 362 synchronized (sBgDataModel) { 363 sBgDataModel.workspaceScreens.clear(); 364 sBgDataModel.workspaceScreens.addAll(screensCopy); 365 } 366 } 367 }; 368 runOnWorkerThread(r); 369 } 370 371 /** 372 * Set this as the current Launcher activity object for the loader. 373 */ 374 public void initialize(Callbacks callbacks) { 375 synchronized (mLock) { 376 Preconditions.assertUIThread(); 377 // Remove any queued UI runnables 378 mHandler.cancelAll(); 379 mCallbacks = new WeakReference<>(callbacks); 380 } 381 } 382 383 @Override 384 public void onPackageChanged(String packageName, UserHandle user) { 385 int op = PackageUpdatedTask.OP_UPDATE; 386 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 387 } 388 389 @Override 390 public void onPackageRemoved(String packageName, UserHandle user) { 391 onPackagesRemoved(user, packageName); 392 } 393 394 public void onPackagesRemoved(UserHandle user, String... packages) { 395 int op = PackageUpdatedTask.OP_REMOVE; 396 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); 397 } 398 399 @Override 400 public void onPackageAdded(String packageName, UserHandle user) { 401 int op = PackageUpdatedTask.OP_ADD; 402 enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); 403 } 404 405 @Override 406 public void onPackagesAvailable(String[] packageNames, UserHandle user, 407 boolean replacing) { 408 enqueueModelUpdateTask( 409 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); 410 } 411 412 @Override 413 public void onPackagesUnavailable(String[] packageNames, UserHandle user, 414 boolean replacing) { 415 if (!replacing) { 416 enqueueModelUpdateTask(new PackageUpdatedTask( 417 PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); 418 } 419 } 420 421 @Override 422 public void onPackagesSuspended(String[] packageNames, UserHandle user) { 423 enqueueModelUpdateTask(new PackageUpdatedTask( 424 PackageUpdatedTask.OP_SUSPEND, user, packageNames)); 425 } 426 427 @Override 428 public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { 429 enqueueModelUpdateTask(new PackageUpdatedTask( 430 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); 431 } 432 433 @Override 434 public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, 435 UserHandle user) { 436 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); 437 } 438 439 public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, 440 UserHandle user) { 441 enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); 442 } 443 444 /** 445 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 446 * ACTION_PACKAGE_CHANGED. 447 */ 448 @Override 449 public void onReceive(Context context, Intent intent) { 450 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); 451 452 final String action = intent.getAction(); 453 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 454 // If we have changed locale we need to clear out the labels in all apps/workspace. 455 forceReload(); 456 } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) 457 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 458 UserManagerCompat.getInstance(context).enableAndResetCache(); 459 forceReload(); 460 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 461 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 462 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 463 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); 464 if (user != null) { 465 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 466 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { 467 enqueueModelUpdateTask(new PackageUpdatedTask( 468 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); 469 } 470 471 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so 472 // we need to run the state change task again. 473 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 474 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 475 enqueueModelUpdateTask(new UserLockStateChangedTask(user)); 476 } 477 } 478 } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { 479 ExtractionUtils.startColorExtractionServiceIfNecessary(context); 480 } 481 } 482 483 /** 484 * Reloads the workspace items from the DB and re-binds the workspace. This should generally 485 * not be called as DB updates are automatically followed by UI update 486 */ 487 public void forceReload() { 488 synchronized (mLock) { 489 // Stop any existing loaders first, so they don't set mModelLoaded to true later 490 stopLoaderLocked(); 491 mModelLoaded = false; 492 } 493 494 // Do this here because if the launcher activity is running it will be restarted. 495 // If it's not running startLoaderFromBackground will merely tell it that it needs 496 // to reload. 497 startLoaderFromBackground(); 498 } 499 500 /** 501 * When the launcher is in the background, it's possible for it to miss paired 502 * configuration changes. So whenever we trigger the loader from the background 503 * tell the launcher that it needs to re-run the loader when it comes back instead 504 * of doing it now. 505 */ 506 public void startLoaderFromBackground() { 507 Callbacks callbacks = getCallback(); 508 if (callbacks != null) { 509 // Only actually run the loader if they're not paused. 510 if (!callbacks.setLoadOnResume()) { 511 startLoader(callbacks.getCurrentWorkspaceScreen()); 512 } 513 } 514 } 515 516 /** 517 * If there is already a loader task running, tell it to stop. 518 */ 519 private void stopLoaderLocked() { 520 LoaderTask oldTask = mLoaderTask; 521 if (oldTask != null) { 522 oldTask.stopLocked(); 523 } 524 } 525 526 public boolean isCurrentCallbacks(Callbacks callbacks) { 527 return (mCallbacks != null && mCallbacks.get() == callbacks); 528 } 529 530 /** 531 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 532 * @return true if the page could be bound synchronously. 533 */ 534 public boolean startLoader(int synchronousBindPage) { 535 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 536 InstallShortcutReceiver.enableInstallQueue(); 537 synchronized (mLock) { 538 // Don't bother to start the thread if we know it's not going to do anything 539 if (mCallbacks != null && mCallbacks.get() != null) { 540 final Callbacks oldCallbacks = mCallbacks.get(); 541 // Clear any pending bind-runnables from the synchronized load process. 542 runOnMainThread(new Runnable() { 543 public void run() { 544 oldCallbacks.clearPendingBinds(); 545 } 546 }); 547 548 // If there is already one running, tell it to stop. 549 stopLoaderLocked(); 550 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage); 551 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE 552 && mModelLoaded && !mIsLoaderTaskRunning) { 553 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 554 return true; 555 } else { 556 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 557 sWorker.post(mLoaderTask); 558 } 559 } 560 } 561 return false; 562 } 563 564 public void stopLoader() { 565 synchronized (mLock) { 566 if (mLoaderTask != null) { 567 mLoaderTask.stopLocked(); 568 } 569 } 570 } 571 572 /** 573 * Loads the workspace screen ids in an ordered list. 574 */ 575 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) { 576 final ContentResolver contentResolver = context.getContentResolver(); 577 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 578 579 // Get screens ordered by rank. 580 return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query( 581 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); 582 } 583 584 /** 585 * Runnable for the thread that loads the contents of the launcher: 586 * - workspace icons 587 * - widgets 588 * - all apps icons 589 * - deep shortcuts within apps 590 */ 591 private class LoaderTask implements Runnable { 592 private Context mContext; 593 private int mPageToBindFirst; 594 595 @Thunk boolean mIsLoadingAndBindingWorkspace; 596 private boolean mStopped; 597 @Thunk boolean mLoadAndBindStepFinished; 598 599 LoaderTask(Context context, int pageToBindFirst) { 600 mContext = context; 601 mPageToBindFirst = pageToBindFirst; 602 } 603 604 private void waitForIdle() { 605 // Wait until the either we're stopped or the other threads are done. 606 // This way we don't start loading all apps until the workspace has settled 607 // down. 608 synchronized (LoaderTask.this) { 609 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 610 611 mHandler.postIdle(new Runnable() { 612 public void run() { 613 synchronized (LoaderTask.this) { 614 mLoadAndBindStepFinished = true; 615 if (DEBUG_LOADERS) { 616 Log.d(TAG, "done with previous binding step"); 617 } 618 LoaderTask.this.notify(); 619 } 620 } 621 }); 622 623 while (!mStopped && !mLoadAndBindStepFinished) { 624 try { 625 // Just in case mFlushingWorkerThread changes but we aren't woken up, 626 // wait no longer than 1sec at a time 627 this.wait(1000); 628 } catch (InterruptedException ex) { 629 // Ignore 630 } 631 } 632 if (DEBUG_LOADERS) { 633 Log.d(TAG, "waited " 634 + (SystemClock.uptimeMillis()-workspaceWaitTime) 635 + "ms for previous step to finish binding"); 636 } 637 } 638 } 639 640 void runBindSynchronousPage(int synchronousBindPage) { 641 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) { 642 // Ensure that we have a valid page index to load synchronously 643 throw new RuntimeException("Should not call runBindSynchronousPage() without " + 644 "valid page index"); 645 } 646 if (!mModelLoaded) { 647 // Ensure that we don't try and bind a specified page when the pages have not been 648 // loaded already (we should load everything asynchronously in that case) 649 throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); 650 } 651 synchronized (mLock) { 652 if (mIsLoaderTaskRunning) { 653 // Ensure that we are never running the background loading at this point since 654 // we also touch the background collections 655 throw new RuntimeException("Error! Background loading is already running"); 656 } 657 } 658 659 // XXX: Throw an exception if we are already loading (since we touch the worker thread 660 // data structures, we can't allow any other thread to touch that data, but because 661 // this call is synchronous, we can get away with not locking). 662 663 // The LauncherModel is static in the LauncherAppState and mHandler may have queued 664 // operations from the previous activity. We need to ensure that all queued operations 665 // are executed before any synchronous binding work is done. 666 mHandler.flush(); 667 668 // Divide the set of loaded items into those that we are binding synchronously, and 669 // everything else that is to be bound normally (asynchronously). 670 bindWorkspace(synchronousBindPage); 671 // XXX: For now, continue posting the binding of AllApps as there are other issues that 672 // arise from that. 673 onlyBindAllApps(); 674 675 bindDeepShortcuts(); 676 } 677 678 private void verifyNotStopped() throws CancellationException { 679 synchronized (LoaderTask.this) { 680 if (mStopped) { 681 throw new CancellationException("Loader stopped"); 682 } 683 } 684 } 685 686 public void run() { 687 synchronized (mLock) { 688 if (mStopped) { 689 return; 690 } 691 mIsLoaderTaskRunning = true; 692 } 693 694 try { 695 if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); 696 // Set to false in bindWorkspace() 697 mIsLoadingAndBindingWorkspace = true; 698 loadWorkspace(); 699 700 verifyNotStopped(); 701 if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace"); 702 bindWorkspace(mPageToBindFirst); 703 704 // Take a break 705 if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle"); 706 waitForIdle(); 707 verifyNotStopped(); 708 709 // second step 710 if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps"); 711 loadAllApps(); 712 713 verifyNotStopped(); 714 if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache"); 715 updateIconCache(); 716 717 // Take a break 718 if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle"); 719 waitForIdle(); 720 verifyNotStopped(); 721 722 // third step 723 if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts"); 724 loadDeepShortcuts(); 725 726 verifyNotStopped(); 727 if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts"); 728 bindDeepShortcuts(); 729 730 synchronized (mLock) { 731 // Everything loaded bind the data. 732 mModelLoaded = true; 733 mHasLoaderCompletedOnce = true; 734 } 735 } catch (CancellationException e) { 736 // Loader stopped, ignore 737 } finally { 738 // Clear out this reference, otherwise we end up holding it until all of the 739 // callback runnables are done. 740 mContext = null; 741 742 synchronized (mLock) { 743 // If we are still the last one to be scheduled, remove ourselves. 744 if (mLoaderTask == this) { 745 mLoaderTask = null; 746 } 747 mIsLoaderTaskRunning = false; 748 } 749 } 750 } 751 752 public void stopLocked() { 753 synchronized (LoaderTask.this) { 754 mStopped = true; 755 this.notify(); 756 } 757 } 758 759 /** 760 * Gets the callbacks object. If we've been stopped, or if the launcher object 761 * has somehow been garbage collected, return null instead. Pass in the Callbacks 762 * object that was around when the deferred message was scheduled, and if there's 763 * a new Callbacks object around then also return null. This will save us from 764 * calling onto it with data that will be ignored. 765 */ 766 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 767 synchronized (mLock) { 768 if (mStopped) { 769 return null; 770 } 771 772 if (mCallbacks == null) { 773 return null; 774 } 775 776 final Callbacks callbacks = mCallbacks.get(); 777 if (callbacks != oldCallbacks) { 778 return null; 779 } 780 if (callbacks == null) { 781 Log.w(TAG, "no mCallbacks"); 782 return null; 783 } 784 785 return callbacks; 786 } 787 } 788 789 private void loadWorkspace() { 790 if (LauncherAppState.PROFILE_STARTUP) { 791 Trace.beginSection("Loading Workspace"); 792 } 793 794 final Context context = mContext; 795 final ContentResolver contentResolver = context.getContentResolver(); 796 final PackageManagerHelper pmHelper = new PackageManagerHelper(context); 797 final boolean isSafeMode = pmHelper.isSafeMode(); 798 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 799 final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context); 800 final boolean isSdCardReady = Utilities.isBootCompleted(); 801 final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>(); 802 803 boolean clearDb = false; 804 try { 805 ImportDataTask.performImportIfPossible(context); 806 } catch (Exception e) { 807 // Migration failed. Clear workspace. 808 clearDb = true; 809 } 810 811 if (!clearDb && GridSizeMigrationTask.ENABLED && 812 !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { 813 // Migration failed. Clear workspace. 814 clearDb = true; 815 } 816 817 if (clearDb) { 818 Log.d(TAG, "loadWorkspace: resetting launcher database"); 819 LauncherSettings.Settings.call(contentResolver, 820 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 821 } 822 823 Log.d(TAG, "loadWorkspace: loading default favorites"); 824 LauncherSettings.Settings.call(contentResolver, 825 LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); 826 827 synchronized (sBgDataModel) { 828 sBgDataModel.clear(); 829 830 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat 831 .getInstance(mContext).updateAndGetActiveSessionCache(); 832 sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); 833 834 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); 835 final LoaderCursor c = new LoaderCursor(contentResolver.query( 836 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp); 837 838 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; 839 840 try { 841 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 842 LauncherSettings.Favorites.APPWIDGET_ID); 843 final int appWidgetProviderIndex = c.getColumnIndexOrThrow( 844 LauncherSettings.Favorites.APPWIDGET_PROVIDER); 845 final int spanXIndex = c.getColumnIndexOrThrow 846 (LauncherSettings.Favorites.SPANX); 847 final int spanYIndex = c.getColumnIndexOrThrow( 848 LauncherSettings.Favorites.SPANY); 849 final int rankIndex = c.getColumnIndexOrThrow( 850 LauncherSettings.Favorites.RANK); 851 final int optionsIndex = c.getColumnIndexOrThrow( 852 LauncherSettings.Favorites.OPTIONS); 853 854 final LongSparseArray<UserHandle> allUsers = c.allUsers; 855 final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); 856 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); 857 for (UserHandle user : mUserManager.getUserProfiles()) { 858 long serialNo = mUserManager.getSerialNumberForUser(user); 859 allUsers.put(serialNo, user); 860 quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); 861 862 boolean userUnlocked = mUserManager.isUserUnlocked(user); 863 864 // We can only query for shortcuts when the user is unlocked. 865 if (userUnlocked) { 866 List<ShortcutInfoCompat> pinnedShortcuts = 867 shortcutManager.queryForPinnedShortcuts(null, user); 868 if (shortcutManager.wasLastCallSuccess()) { 869 for (ShortcutInfoCompat shortcut : pinnedShortcuts) { 870 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), 871 shortcut); 872 } 873 } else { 874 // Shortcut manager can fail due to some race condition when the 875 // lock state changes too frequently. For the purpose of the loading 876 // shortcuts, consider the user is still locked. 877 userUnlocked = false; 878 } 879 } 880 unlockedUsers.put(serialNo, userUnlocked); 881 } 882 883 ShortcutInfo info; 884 LauncherAppWidgetInfo appWidgetInfo; 885 Intent intent; 886 String targetPkg; 887 888 while (!mStopped && c.moveToNext()) { 889 try { 890 if (c.user == null) { 891 // User has been deleted, remove the item. 892 c.markDeleted("User has been deleted"); 893 continue; 894 } 895 896 boolean allowMissingTarget = false; 897 switch (c.itemType) { 898 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 899 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 900 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 901 intent = c.parseIntent(); 902 if (intent == null) { 903 c.markDeleted("Invalid or null intent"); 904 continue; 905 } 906 907 int disabledState = quietMode.get(c.serialNumber) ? 908 ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0; 909 ComponentName cn = intent.getComponent(); 910 targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); 911 912 if (!Process.myUserHandle().equals(c.user)) { 913 if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 914 c.markDeleted("Legacy shortcuts are only allowed for default user"); 915 continue; 916 } else if (c.restoreFlag != 0) { 917 // Don't restore items for other profiles. 918 c.markDeleted("Restore from managed profile not supported"); 919 continue; 920 } 921 } 922 if (TextUtils.isEmpty(targetPkg) && 923 c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 924 c.markDeleted("Only legacy shortcuts can have null package"); 925 continue; 926 } 927 928 // If there is no target package, its an implicit intent 929 // (legacy shortcut) which is always valid 930 boolean validTarget = TextUtils.isEmpty(targetPkg) || 931 launcherApps.isPackageEnabledForProfile(targetPkg, c.user); 932 933 if (cn != null && validTarget) { 934 // If the apk is present and the shortcut points to a specific 935 // component. 936 937 // If the component is already present 938 if (launcherApps.isActivityEnabledForProfile(cn, c.user)) { 939 // no special handling necessary for this item 940 c.markRestored(); 941 } else { 942 if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { 943 // We allow auto install apps to have their intent 944 // updated after an install. 945 intent = pmHelper.getAppLaunchIntent(targetPkg, c.user); 946 if (intent != null) { 947 c.restoreFlag = 0; 948 c.updater().put( 949 LauncherSettings.Favorites.INTENT, 950 intent.toUri(0)).commit(); 951 cn = intent.getComponent(); 952 } else { 953 c.markDeleted("Unable to find a launch target"); 954 continue; 955 } 956 } else { 957 // The app is installed but the component is no 958 // longer available. 959 c.markDeleted("Invalid component removed: " + cn); 960 continue; 961 } 962 } 963 } 964 // else if cn == null => can't infer much, leave it 965 // else if !validPkg => could be restored icon or missing sd-card 966 967 if (!TextUtils.isEmpty(targetPkg) && !validTarget) { 968 // Points to a valid app (superset of cn != null) but the apk 969 // is not available. 970 971 if (c.restoreFlag != 0) { 972 // Package is not yet available but might be 973 // installed later. 974 FileLog.d(TAG, "package not yet restored: " + targetPkg); 975 976 if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) { 977 // Restore has started once. 978 } else if (installingPkgs.containsKey(targetPkg)) { 979 // App restore has started. Update the flag 980 c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED; 981 c.updater().commit(); 982 } else { 983 c.markDeleted("Unrestored app removed: " + targetPkg); 984 continue; 985 } 986 } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) { 987 // Package is present but not available. 988 disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; 989 // Add the icon on the workspace anyway. 990 allowMissingTarget = true; 991 } else if (!isSdCardReady) { 992 // SdCard is not ready yet. Package might get available, 993 // once it is ready. 994 Log.d(TAG, "Missing pkg, will check later: " + targetPkg); 995 pendingPackages.addToList(c.user, targetPkg); 996 // Add the icon on the workspace anyway. 997 allowMissingTarget = true; 998 } else { 999 // Do not wait for external media load anymore. 1000 c.markDeleted("Invalid package removed: " + targetPkg); 1001 continue; 1002 } 1003 } 1004 1005 if (validTarget) { 1006 // The shortcut points to a valid target (either no target 1007 // or something which is ready to be used) 1008 c.markRestored(); 1009 } 1010 1011 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && 1012 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; 1013 1014 if (c.restoreFlag != 0) { 1015 // Already verified above that user is same as default user 1016 info = c.getRestoredItemInfo(intent); 1017 } else if (c.itemType == 1018 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1019 info = c.getAppShortcutInfo( 1020 intent, allowMissingTarget, useLowResIcon); 1021 } else if (c.itemType == 1022 LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 1023 1024 ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); 1025 if (unlockedUsers.get(c.serialNumber)) { 1026 ShortcutInfoCompat pinnedShortcut = 1027 shortcutKeyToPinnedShortcuts.get(key); 1028 if (pinnedShortcut == null) { 1029 // The shortcut is no longer valid. 1030 c.markDeleted("Pinned shortcut not found"); 1031 continue; 1032 } 1033 info = new ShortcutInfo(pinnedShortcut, context); 1034 info.iconBitmap = LauncherIcons 1035 .createShortcutIcon(pinnedShortcut, context); 1036 if (pmHelper.isAppSuspended( 1037 pinnedShortcut.getPackage(), info.user)) { 1038 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; 1039 } 1040 intent = info.intent; 1041 } else { 1042 // Create a shortcut info in disabled mode for now. 1043 info = c.loadSimpleShortcut(); 1044 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; 1045 } 1046 } else { // item type == ITEM_TYPE_SHORTCUT 1047 info = c.loadSimpleShortcut(); 1048 1049 // Shortcuts are only available on the primary profile 1050 if (pmHelper.isAppSuspended(targetPkg, c.user)) { 1051 disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; 1052 } 1053 1054 // App shortcuts that used to be automatically added to Launcher 1055 // didn't always have the correct intent flags set, so do that 1056 // here 1057 if (intent.getAction() != null && 1058 intent.getCategories() != null && 1059 intent.getAction().equals(Intent.ACTION_MAIN) && 1060 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 1061 intent.addFlags( 1062 Intent.FLAG_ACTIVITY_NEW_TASK | 1063 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1064 } 1065 } 1066 1067 if (info != null) { 1068 c.applyCommonProperties(info); 1069 1070 info.intent = intent; 1071 info.rank = c.getInt(rankIndex); 1072 info.spanX = 1; 1073 info.spanY = 1; 1074 info.isDisabled |= disabledState; 1075 if (isSafeMode && !Utilities.isSystemApp(context, intent)) { 1076 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; 1077 } 1078 1079 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { 1080 Integer progress = installingPkgs.get(targetPkg); 1081 if (progress != null) { 1082 info.setInstallProgress(progress); 1083 } else { 1084 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; 1085 } 1086 } 1087 1088 c.checkAndAddItem(info, sBgDataModel); 1089 } else { 1090 throw new RuntimeException("Unexpected null ShortcutInfo"); 1091 } 1092 break; 1093 1094 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1095 FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id); 1096 c.applyCommonProperties(folderInfo); 1097 1098 // Do not trim the folder label, as is was set by the user. 1099 folderInfo.title = c.getString(c.titleIndex); 1100 folderInfo.spanX = 1; 1101 folderInfo.spanY = 1; 1102 folderInfo.options = c.getInt(optionsIndex); 1103 1104 // no special handling required for restored folders 1105 c.markRestored(); 1106 1107 c.checkAndAddItem(folderInfo, sBgDataModel); 1108 break; 1109 1110 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1111 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 1112 // Read all Launcher-specific widget details 1113 boolean customWidget = c.itemType == 1114 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 1115 1116 int appWidgetId = c.getInt(appWidgetIdIndex); 1117 String savedProvider = c.getString(appWidgetProviderIndex); 1118 1119 final ComponentName component = 1120 ComponentName.unflattenFromString(savedProvider); 1121 1122 final boolean isIdValid = !c.hasRestoreFlag( 1123 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 1124 final boolean wasProviderReady = !c.hasRestoreFlag( 1125 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY); 1126 1127 if (widgetProvidersMap == null) { 1128 widgetProvidersMap = AppWidgetManagerCompat 1129 .getInstance(mContext).getAllProvidersMap(); 1130 } 1131 final AppWidgetProviderInfo provider = widgetProvidersMap.get( 1132 new ComponentKey( 1133 ComponentName.unflattenFromString(savedProvider), 1134 c.user)); 1135 1136 final boolean isProviderReady = isValidProvider(provider); 1137 if (!isSafeMode && !customWidget && 1138 wasProviderReady && !isProviderReady) { 1139 c.markDeleted( 1140 "Deleting widget that isn't installed anymore: " 1141 + provider); 1142 } else { 1143 if (isProviderReady) { 1144 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 1145 provider.provider); 1146 1147 // The provider is available. So the widget is either 1148 // available or not available. We do not need to track 1149 // any future restore updates. 1150 int status = c.restoreFlag & 1151 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 1152 if (!wasProviderReady) { 1153 // If provider was not previously ready, update the 1154 // status and UI flag. 1155 1156 // Id would be valid only if the widget restore broadcast was received. 1157 if (isIdValid) { 1158 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 1159 } else { 1160 status &= ~LauncherAppWidgetInfo 1161 .FLAG_PROVIDER_NOT_READY; 1162 } 1163 } 1164 appWidgetInfo.restoreStatus = status; 1165 } else { 1166 Log.v(TAG, "Widget restore pending id=" + c.id 1167 + " appWidgetId=" + appWidgetId 1168 + " status =" + c.restoreFlag); 1169 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 1170 component); 1171 appWidgetInfo.restoreStatus = c.restoreFlag; 1172 Integer installProgress = installingPkgs.get(component.getPackageName()); 1173 1174 if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) { 1175 // Restore has started once. 1176 } else if (installProgress != null) { 1177 // App restore has started. Update the flag 1178 appWidgetInfo.restoreStatus |= 1179 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 1180 } else if (!isSafeMode) { 1181 c.markDeleted("Unrestored widget removed: " + component); 1182 continue; 1183 } 1184 1185 appWidgetInfo.installProgress = 1186 installProgress == null ? 0 : installProgress; 1187 } 1188 if (appWidgetInfo.hasRestoreFlag( 1189 LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { 1190 appWidgetInfo.bindOptions = c.parseIntent(); 1191 } 1192 1193 c.applyCommonProperties(appWidgetInfo); 1194 appWidgetInfo.spanX = c.getInt(spanXIndex); 1195 appWidgetInfo.spanY = c.getInt(spanYIndex); 1196 appWidgetInfo.user = c.user; 1197 1198 if (!c.isOnWorkspaceOrHotseat()) { 1199 c.markDeleted("Widget found where container != " + 1200 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 1201 continue; 1202 } 1203 1204 if (!customWidget) { 1205 String providerName = 1206 appWidgetInfo.providerName.flattenToString(); 1207 if (!providerName.equals(savedProvider) || 1208 (appWidgetInfo.restoreStatus != c.restoreFlag)) { 1209 c.updater() 1210 .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, 1211 providerName) 1212 .put(LauncherSettings.Favorites.RESTORED, 1213 appWidgetInfo.restoreStatus) 1214 .commit(); 1215 } 1216 } 1217 c.checkAndAddItem(appWidgetInfo, sBgDataModel); 1218 } 1219 break; 1220 } 1221 } catch (Exception e) { 1222 Log.e(TAG, "Desktop items loading interrupted", e); 1223 } 1224 } 1225 } finally { 1226 Utilities.closeSilently(c); 1227 } 1228 1229 // Break early if we've stopped loading 1230 if (mStopped) { 1231 sBgDataModel.clear(); 1232 return; 1233 } 1234 1235 // Remove dead items 1236 if (c.commitDeleted()) { 1237 // Remove any empty folder 1238 ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings 1239 .call(contentResolver, 1240 LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) 1241 .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); 1242 for (long folderId : deletedFolderIds) { 1243 sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId)); 1244 sBgDataModel.folders.remove(folderId); 1245 sBgDataModel.itemsIdMap.remove(folderId); 1246 } 1247 } 1248 1249 // Unpin shortcuts that don't exist on the workspace. 1250 HashSet<ShortcutKey> pendingShortcuts = 1251 InstallShortcutReceiver.getPendingShortcuts(context); 1252 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { 1253 MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key); 1254 if ((numTimesPinned == null || numTimesPinned.value == 0) 1255 && !pendingShortcuts.contains(key)) { 1256 // Shortcut is pinned but doesn't exist on the workspace; unpin it. 1257 shortcutManager.unpinShortcut(key); 1258 } 1259 } 1260 1261 // Sort all the folder items and make sure the first 3 items are high resolution. 1262 for (FolderInfo folder : sBgDataModel.folders) { 1263 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); 1264 int pos = 0; 1265 for (ShortcutInfo info : folder.contents) { 1266 if (info.usingLowResIcon && 1267 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1268 mIconCache.getTitleAndIcon(info, false); 1269 } 1270 pos ++; 1271 if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { 1272 break; 1273 } 1274 } 1275 } 1276 1277 c.commitRestoredItems(); 1278 if (!isSdCardReady && !pendingPackages.isEmpty()) { 1279 context.registerReceiver( 1280 new SdCardAvailableReceiver( 1281 LauncherModel.this, mContext, pendingPackages), 1282 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), 1283 null, 1284 sWorker); 1285 } 1286 1287 // Remove any empty screens 1288 ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens); 1289 for (ItemInfo item: sBgDataModel.itemsIdMap) { 1290 long screenId = item.screenId; 1291 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1292 unusedScreens.contains(screenId)) { 1293 unusedScreens.remove(screenId); 1294 } 1295 } 1296 1297 // If there are any empty screens remove them, and update. 1298 if (unusedScreens.size() != 0) { 1299 sBgDataModel.workspaceScreens.removeAll(unusedScreens); 1300 updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens); 1301 } 1302 } 1303 if (LauncherAppState.PROFILE_STARTUP) { 1304 Trace.endSection(); 1305 } 1306 } 1307 1308 /** Filters the set of items who are directly or indirectly (via another container) on the 1309 * specified screen. */ 1310 private void filterCurrentWorkspaceItems(long currentScreenId, 1311 ArrayList<ItemInfo> allWorkspaceItems, 1312 ArrayList<ItemInfo> currentScreenItems, 1313 ArrayList<ItemInfo> otherScreenItems) { 1314 // Purge any null ItemInfos 1315 Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); 1316 while (iter.hasNext()) { 1317 ItemInfo i = iter.next(); 1318 if (i == null) { 1319 iter.remove(); 1320 } 1321 } 1322 1323 // Order the set of items by their containers first, this allows use to walk through the 1324 // list sequentially, build up a list of containers that are in the specified screen, 1325 // as well as all items in those containers. 1326 Set<Long> itemsOnScreen = new HashSet<Long>(); 1327 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 1328 @Override 1329 public int compare(ItemInfo lhs, ItemInfo rhs) { 1330 return Utilities.longCompare(lhs.container, rhs.container); 1331 } 1332 }); 1333 for (ItemInfo info : allWorkspaceItems) { 1334 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1335 if (info.screenId == currentScreenId) { 1336 currentScreenItems.add(info); 1337 itemsOnScreen.add(info.id); 1338 } else { 1339 otherScreenItems.add(info); 1340 } 1341 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1342 currentScreenItems.add(info); 1343 itemsOnScreen.add(info.id); 1344 } else { 1345 if (itemsOnScreen.contains(info.container)) { 1346 currentScreenItems.add(info); 1347 itemsOnScreen.add(info.id); 1348 } else { 1349 otherScreenItems.add(info); 1350 } 1351 } 1352 } 1353 } 1354 1355 /** Filters the set of widgets which are on the specified screen. */ 1356 private void filterCurrentAppWidgets(long currentScreenId, 1357 ArrayList<LauncherAppWidgetInfo> appWidgets, 1358 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, 1359 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { 1360 1361 for (LauncherAppWidgetInfo widget : appWidgets) { 1362 if (widget == null) continue; 1363 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1364 widget.screenId == currentScreenId) { 1365 currentScreenWidgets.add(widget); 1366 } else { 1367 otherScreenWidgets.add(widget); 1368 } 1369 } 1370 } 1371 1372 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 1373 * right) */ 1374 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 1375 final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); 1376 final int screenCols = profile.numColumns; 1377 final int screenCellCount = profile.numColumns * profile.numRows; 1378 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 1379 @Override 1380 public int compare(ItemInfo lhs, ItemInfo rhs) { 1381 if (lhs.container == rhs.container) { 1382 // Within containers, order by their spatial position in that container 1383 switch ((int) lhs.container) { 1384 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 1385 long lr = (lhs.screenId * screenCellCount + 1386 lhs.cellY * screenCols + lhs.cellX); 1387 long rr = (rhs.screenId * screenCellCount + 1388 rhs.cellY * screenCols + rhs.cellX); 1389 return Utilities.longCompare(lr, rr); 1390 } 1391 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 1392 // We currently use the screen id as the rank 1393 return Utilities.longCompare(lhs.screenId, rhs.screenId); 1394 } 1395 default: 1396 if (ProviderConfig.IS_DOGFOOD_BUILD) { 1397 throw new RuntimeException("Unexpected container type when " + 1398 "sorting workspace items."); 1399 } 1400 return 0; 1401 } 1402 } else { 1403 // Between containers, order by hotseat, desktop 1404 return Utilities.longCompare(lhs.container, rhs.container); 1405 } 1406 } 1407 }); 1408 } 1409 1410 private void bindWorkspaceScreens(final Callbacks oldCallbacks, 1411 final ArrayList<Long> orderedScreens) { 1412 final Runnable r = new Runnable() { 1413 @Override 1414 public void run() { 1415 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1416 if (callbacks != null) { 1417 callbacks.bindScreens(orderedScreens); 1418 } 1419 } 1420 }; 1421 runOnMainThread(r); 1422 } 1423 1424 private void bindWorkspaceItems(final Callbacks oldCallbacks, 1425 final ArrayList<ItemInfo> workspaceItems, 1426 final ArrayList<LauncherAppWidgetInfo> appWidgets, 1427 final Executor executor) { 1428 1429 // Bind the workspace items 1430 int N = workspaceItems.size(); 1431 for (int i = 0; i < N; i += ITEMS_CHUNK) { 1432 final int start = i; 1433 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 1434 final Runnable r = new Runnable() { 1435 @Override 1436 public void run() { 1437 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1438 if (callbacks != null) { 1439 callbacks.bindItems(workspaceItems, start, start+chunkSize, 1440 false); 1441 } 1442 } 1443 }; 1444 executor.execute(r); 1445 } 1446 1447 // Bind the widgets, one at a time 1448 N = appWidgets.size(); 1449 for (int i = 0; i < N; i++) { 1450 final LauncherAppWidgetInfo widget = appWidgets.get(i); 1451 final Runnable r = new Runnable() { 1452 public void run() { 1453 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1454 if (callbacks != null) { 1455 callbacks.bindAppWidget(widget); 1456 } 1457 } 1458 }; 1459 executor.execute(r); 1460 } 1461 } 1462 1463 /** 1464 * Binds all loaded data to actual views on the main thread. 1465 */ 1466 private void bindWorkspace(int synchronizeBindPage) { 1467 final long t = SystemClock.uptimeMillis(); 1468 Runnable r; 1469 1470 // Don't use these two variables in any of the callback runnables. 1471 // Otherwise we hold a reference to them. 1472 final Callbacks oldCallbacks = mCallbacks.get(); 1473 if (oldCallbacks == null) { 1474 // This launcher has exited and nobody bothered to tell us. Just bail. 1475 Log.w(TAG, "LoaderTask running with no launcher"); 1476 return; 1477 } 1478 1479 // Save a copy of all the bg-thread collections 1480 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 1481 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 1482 ArrayList<Long> orderedScreenIds = new ArrayList<>(); 1483 1484 synchronized (sBgDataModel) { 1485 workspaceItems.addAll(sBgDataModel.workspaceItems); 1486 appWidgets.addAll(sBgDataModel.appWidgets); 1487 orderedScreenIds.addAll(sBgDataModel.workspaceScreens); 1488 } 1489 1490 final int currentScreen; 1491 { 1492 int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE 1493 ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen(); 1494 if (currScreen >= orderedScreenIds.size()) { 1495 // There may be no workspace screens (just hotseat items and an empty page). 1496 currScreen = PagedView.INVALID_RESTORE_PAGE; 1497 } 1498 currentScreen = currScreen; 1499 } 1500 final boolean validFirstPage = currentScreen >= 0; 1501 final long currentScreenId = 1502 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 1503 1504 // Separate the items that are on the current screen, and all the other remaining items 1505 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 1506 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 1507 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 1508 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 1509 1510 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 1511 otherWorkspaceItems); 1512 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, 1513 otherAppWidgets); 1514 sortWorkspaceItemsSpatially(currentWorkspaceItems); 1515 sortWorkspaceItemsSpatially(otherWorkspaceItems); 1516 1517 // Tell the workspace that we're about to start binding items 1518 r = new Runnable() { 1519 public void run() { 1520 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1521 if (callbacks != null) { 1522 callbacks.clearPendingBinds(); 1523 callbacks.startBinding(); 1524 } 1525 } 1526 }; 1527 runOnMainThread(r); 1528 1529 bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 1530 1531 Executor mainExecutor = new DeferredMainThreadExecutor(); 1532 // Load items on the current page. 1533 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); 1534 1535 // In case of validFirstPage, only bind the first screen, and defer binding the 1536 // remaining screens after first onDraw (and an optional the fade animation whichever 1537 // happens later). 1538 // This ensures that the first screen is immediately visible (eg. during rotation) 1539 // In case of !validFirstPage, bind all pages one after other. 1540 final Executor deferredExecutor = 1541 validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor; 1542 1543 mainExecutor.execute(new Runnable() { 1544 @Override 1545 public void run() { 1546 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1547 if (callbacks != null) { 1548 callbacks.finishFirstPageBind( 1549 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); 1550 } 1551 } 1552 }); 1553 1554 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor); 1555 1556 // Tell the workspace that we're done binding items 1557 r = new Runnable() { 1558 public void run() { 1559 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1560 if (callbacks != null) { 1561 callbacks.finishBindingItems(); 1562 } 1563 1564 mIsLoadingAndBindingWorkspace = false; 1565 1566 // Run all the bind complete runnables after workspace is bound. 1567 if (!mBindCompleteRunnables.isEmpty()) { 1568 synchronized (mBindCompleteRunnables) { 1569 for (final Runnable r : mBindCompleteRunnables) { 1570 runOnWorkerThread(r); 1571 } 1572 mBindCompleteRunnables.clear(); 1573 } 1574 } 1575 1576 // If we're profiling, ensure this is the last thing in the queue. 1577 if (DEBUG_LOADERS) { 1578 Log.d(TAG, "bound workspace in " 1579 + (SystemClock.uptimeMillis()-t) + "ms"); 1580 } 1581 1582 } 1583 }; 1584 deferredExecutor.execute(r); 1585 1586 if (validFirstPage) { 1587 r = new Runnable() { 1588 public void run() { 1589 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1590 if (callbacks != null) { 1591 // We are loading synchronously, which means, some of the pages will be 1592 // bound after first draw. Inform the callbacks that page binding is 1593 // not complete, and schedule the remaining pages. 1594 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 1595 callbacks.onPageBoundSynchronously(currentScreen); 1596 } 1597 callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 1598 } 1599 } 1600 }; 1601 runOnMainThread(r); 1602 } 1603 } 1604 1605 private void updateIconCache() { 1606 // Ignore packages which have a promise icon. 1607 HashSet<String> packagesToIgnore = new HashSet<>(); 1608 synchronized (sBgDataModel) { 1609 for (ItemInfo info : sBgDataModel.itemsIdMap) { 1610 if (info instanceof ShortcutInfo) { 1611 ShortcutInfo si = (ShortcutInfo) info; 1612 if (si.isPromise() && si.getTargetComponent() != null) { 1613 packagesToIgnore.add(si.getTargetComponent().getPackageName()); 1614 } 1615 } else if (info instanceof LauncherAppWidgetInfo) { 1616 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; 1617 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 1618 packagesToIgnore.add(lawi.providerName.getPackageName()); 1619 } 1620 } 1621 } 1622 } 1623 mIconCache.updateDbIcons(packagesToIgnore); 1624 } 1625 1626 private void onlyBindAllApps() { 1627 final Callbacks oldCallbacks = mCallbacks.get(); 1628 if (oldCallbacks == null) { 1629 // This launcher has exited and nobody bothered to tell us. Just bail. 1630 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 1631 return; 1632 } 1633 1634 // shallow copy 1635 @SuppressWarnings("unchecked") 1636 final ArrayList<AppInfo> list 1637 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); 1638 Runnable r = new Runnable() { 1639 public void run() { 1640 final long t = SystemClock.uptimeMillis(); 1641 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1642 if (callbacks != null) { 1643 callbacks.bindAllApplications(list); 1644 } 1645 if (DEBUG_LOADERS) { 1646 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 1647 + (SystemClock.uptimeMillis() - t) + "ms"); 1648 } 1649 } 1650 }; 1651 runOnMainThread(r); 1652 } 1653 1654 private void loadAllApps() { 1655 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1656 1657 final Callbacks oldCallbacks = mCallbacks.get(); 1658 if (oldCallbacks == null) { 1659 // This launcher has exited and nobody bothered to tell us. Just bail. 1660 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); 1661 return; 1662 } 1663 1664 final List<UserHandle> profiles = mUserManager.getUserProfiles(); 1665 1666 // Clear the list of apps 1667 mBgAllAppsList.clear(); 1668 for (UserHandle user : profiles) { 1669 // Query for the set of apps 1670 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1671 final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); 1672 if (DEBUG_LOADERS) { 1673 Log.d(TAG, "getActivityList took " 1674 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); 1675 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); 1676 } 1677 // Fail if we don't have any apps 1678 // TODO: Fix this. Only fail for the current user. 1679 if (apps == null || apps.isEmpty()) { 1680 return; 1681 } 1682 boolean quietMode = mUserManager.isQuietModeEnabled(user); 1683 // Create the ApplicationInfos 1684 for (int i = 0; i < apps.size(); i++) { 1685 LauncherActivityInfo app = apps.get(i); 1686 // This builds the icon bitmaps. 1687 mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); 1688 } 1689 1690 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); 1691 if (heuristic != null) { 1692 final Runnable r = new Runnable() { 1693 1694 @Override 1695 public void run() { 1696 heuristic.processUserApps(apps); 1697 } 1698 }; 1699 runOnMainThread(new Runnable() { 1700 1701 @Override 1702 public void run() { 1703 // Check isLoadingWorkspace on the UI thread, as it is updated on 1704 // the UI thread. 1705 if (mIsLoadingAndBindingWorkspace) { 1706 synchronized (mBindCompleteRunnables) { 1707 mBindCompleteRunnables.add(r); 1708 } 1709 } else { 1710 runOnWorkerThread(r); 1711 } 1712 } 1713 }); 1714 } 1715 } 1716 // Huh? Shouldn't this be inside the Runnable below? 1717 final ArrayList<AppInfo> added = mBgAllAppsList.added; 1718 mBgAllAppsList.added = new ArrayList<AppInfo>(); 1719 1720 // Post callback on main thread 1721 mHandler.post(new Runnable() { 1722 public void run() { 1723 1724 final long bindTime = SystemClock.uptimeMillis(); 1725 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1726 if (callbacks != null) { 1727 callbacks.bindAllApplications(added); 1728 if (DEBUG_LOADERS) { 1729 Log.d(TAG, "bound " + added.size() + " apps in " 1730 + (SystemClock.uptimeMillis() - bindTime) + "ms"); 1731 } 1732 } else { 1733 Log.i(TAG, "not binding apps: no Launcher activity"); 1734 } 1735 } 1736 }); 1737 // Cleanup any data stored for a deleted user. 1738 ManagedProfileHeuristic.processAllUsers(profiles, mContext); 1739 if (DEBUG_LOADERS) { 1740 Log.d(TAG, "Icons processed in " 1741 + (SystemClock.uptimeMillis() - loadTime) + "ms"); 1742 } 1743 } 1744 1745 private void loadDeepShortcuts() { 1746 if (!mModelLoaded) { 1747 sBgDataModel.deepShortcutMap.clear(); 1748 DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext); 1749 mHasShortcutHostPermission = shortcutManager.hasHostPermission(); 1750 if (mHasShortcutHostPermission) { 1751 for (UserHandle user : mUserManager.getUserProfiles()) { 1752 if (mUserManager.isUserUnlocked(user)) { 1753 List<ShortcutInfoCompat> shortcuts = 1754 shortcutManager.queryForAllShortcuts(user); 1755 sBgDataModel.updateDeepShortcutMap(null, user, shortcuts); 1756 } 1757 } 1758 } 1759 } 1760 } 1761 } 1762 1763 public void bindDeepShortcuts() { 1764 final MultiHashMap<ComponentKey, String> shortcutMapCopy = 1765 sBgDataModel.deepShortcutMap.clone(); 1766 Runnable r = new Runnable() { 1767 @Override 1768 public void run() { 1769 Callbacks callbacks = getCallback(); 1770 if (callbacks != null) { 1771 callbacks.bindDeepShortcutMap(shortcutMapCopy); 1772 } 1773 } 1774 }; 1775 runOnMainThread(r); 1776 } 1777 1778 /** 1779 * Refreshes the cached shortcuts if the shortcut permission has changed. 1780 * Current implementation simply reloads the workspace, but it can be optimized to 1781 * use partial updates similar to {@link UserManagerCompat} 1782 */ 1783 public void refreshShortcutsIfRequired() { 1784 if (Utilities.ATLEAST_NOUGAT_MR1) { 1785 sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); 1786 sWorker.post(mShortcutPermissionCheckRunnable); 1787 } 1788 } 1789 1790 /** 1791 * Called when the icons for packages have been updated in the icon cache. 1792 */ 1793 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) { 1794 // If any package icon has changed (app was updated while launcher was dead), 1795 // update the corresponding shortcuts. 1796 enqueueModelUpdateTask(new CacheDataUpdatedTask( 1797 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); 1798 } 1799 1800 void enqueueModelUpdateTask(BaseModelUpdateTask task) { 1801 if (!mModelLoaded && mLoaderTask == null) { 1802 if (DEBUG_LOADERS) { 1803 Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task); 1804 } 1805 return; 1806 } 1807 task.init(this); 1808 runOnWorkerThread(task); 1809 } 1810 1811 /** 1812 * A task to be executed on the current callbacks on the UI thread. 1813 * If there is no current callbacks, the task is ignored. 1814 */ 1815 public interface CallbackTask { 1816 1817 void execute(Callbacks callbacks); 1818 } 1819 1820 /** 1821 * A runnable which changes/updates the data model of the launcher based on certain events. 1822 */ 1823 public static abstract class BaseModelUpdateTask implements Runnable { 1824 1825 private LauncherModel mModel; 1826 private DeferredHandler mUiHandler; 1827 1828 /* package private */ 1829 void init(LauncherModel model) { 1830 mModel = model; 1831 mUiHandler = mModel.mHandler; 1832 } 1833 1834 @Override 1835 public void run() { 1836 if (!mModel.mHasLoaderCompletedOnce) { 1837 // Loader has not yet run. 1838 return; 1839 } 1840 execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList); 1841 } 1842 1843 /** 1844 * Execute the actual task. Called on the worker thread. 1845 */ 1846 public abstract void execute( 1847 LauncherAppState app, BgDataModel dataModel, AllAppsList apps); 1848 1849 /** 1850 * Schedules a {@param task} to be executed on the current callbacks. 1851 */ 1852 public final void scheduleCallbackTask(final CallbackTask task) { 1853 final Callbacks callbacks = mModel.getCallback(); 1854 mUiHandler.post(new Runnable() { 1855 public void run() { 1856 Callbacks cb = mModel.getCallback(); 1857 if (callbacks == cb && cb != null) { 1858 task.execute(callbacks); 1859 } 1860 } 1861 }); 1862 } 1863 1864 public ModelWriter getModelWriter() { 1865 // Updates from model task, do not deal with icon position in hotseat. 1866 return mModel.getWriter(false /* hasVerticalHotseat */); 1867 } 1868 } 1869 1870 public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) { 1871 updateAndBindShortcutInfo(new Provider<ShortcutInfo>() { 1872 @Override 1873 public ShortcutInfo get() { 1874 si.updateFromDeepShortcutInfo(info, mApp.getContext()); 1875 si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext()); 1876 return si; 1877 } 1878 }); 1879 } 1880 1881 /** 1882 * Utility method to update a shortcut on the background thread. 1883 */ 1884 public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) { 1885 enqueueModelUpdateTask(new ExtendedModelTask() { 1886 @Override 1887 public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { 1888 ShortcutInfo info = shortcutProvider.get(); 1889 ArrayList<ShortcutInfo> update = new ArrayList<>(); 1890 update.add(info); 1891 bindUpdatedShortcuts(update, info.user); 1892 } 1893 }); 1894 } 1895 1896 private void bindWidgetsModel(final Callbacks callbacks) { 1897 final MultiHashMap<PackageItemInfo, WidgetItem> widgets 1898 = mBgWidgetsModel.getWidgetsMap().clone(); 1899 mHandler.post(new Runnable() { 1900 @Override 1901 public void run() { 1902 Callbacks cb = getCallback(); 1903 if (callbacks == cb && cb != null) { 1904 callbacks.bindAllWidgets(widgets); 1905 } 1906 } 1907 }); 1908 } 1909 1910 public void refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks, 1911 final boolean bindFirst, @Nullable final PackageUserKey packageUser) { 1912 runOnWorkerThread(new Runnable() { 1913 @Override 1914 public void run() { 1915 if (bindFirst && !mBgWidgetsModel.isEmpty()) { 1916 bindWidgetsModel(callbacks); 1917 } 1918 ArrayList<WidgetItem> widgets = mBgWidgetsModel.update( 1919 mApp.getContext(), packageUser); 1920 bindWidgetsModel(callbacks); 1921 1922 // update the Widget entries inside DB on the worker thread. 1923 mApp.getWidgetCache().removeObsoletePreviews(widgets, packageUser); 1924 } 1925 }); 1926 } 1927 1928 static boolean isValidProvider(AppWidgetProviderInfo provider) { 1929 return (provider != null) && (provider.provider != null) 1930 && (provider.provider.getPackageName() != null); 1931 } 1932 1933 public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 1934 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 1935 writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size()); 1936 for (AppInfo info : mBgAllAppsList.data) { 1937 writer.println(prefix + " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap 1938 + " componentName=" + info.componentName.getPackageName()); 1939 } 1940 } 1941 sBgDataModel.dump(prefix, fd, writer, args); 1942 } 1943 1944 public Callbacks getCallback() { 1945 return mCallbacks != null ? mCallbacks.get() : null; 1946 } 1947 1948 /** 1949 * @return {@link FolderInfo} if its already loaded. 1950 */ 1951 public FolderInfo findFolderById(Long folderId) { 1952 synchronized (sBgDataModel) { 1953 return sBgDataModel.folders.get(folderId); 1954 } 1955 } 1956 1957 @Thunk class DeferredMainThreadExecutor implements Executor { 1958 1959 @Override 1960 public void execute(Runnable command) { 1961 runOnMainThread(command); 1962 } 1963 } 1964 1965 /** 1966 * @return the looper for the worker thread which can be used to start background tasks. 1967 */ 1968 public static Looper getWorkerLooper() { 1969 return sWorkerThread.getLooper(); 1970 } 1971} 1972