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