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