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