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.Intent.ShortcutIconResource; 28import android.content.IntentFilter; 29import android.content.pm.PackageManager; 30import android.content.pm.ResolveInfo; 31import android.database.Cursor; 32import android.graphics.Bitmap; 33import android.net.Uri; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.Looper; 37import android.os.Parcelable; 38import android.os.Process; 39import android.os.SystemClock; 40import android.os.Trace; 41import android.provider.BaseColumns; 42import android.text.TextUtils; 43import android.util.Log; 44import android.util.LongSparseArray; 45import android.util.MutableInt; 46import android.util.Pair; 47 48import com.android.launcher3.compat.AppWidgetManagerCompat; 49import com.android.launcher3.compat.LauncherActivityInfoCompat; 50import com.android.launcher3.compat.LauncherAppsCompat; 51import com.android.launcher3.compat.PackageInstallerCompat; 52import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; 53import com.android.launcher3.compat.UserHandleCompat; 54import com.android.launcher3.compat.UserManagerCompat; 55import com.android.launcher3.config.FeatureFlags; 56import com.android.launcher3.config.ProviderConfig; 57import com.android.launcher3.dynamicui.ExtractionUtils; 58import com.android.launcher3.folder.Folder; 59import com.android.launcher3.folder.FolderIcon; 60import com.android.launcher3.logging.FileLog; 61import com.android.launcher3.model.GridSizeMigrationTask; 62import com.android.launcher3.model.WidgetsModel; 63import com.android.launcher3.provider.ImportDataTask; 64import com.android.launcher3.provider.LauncherDbUtils; 65import com.android.launcher3.shortcuts.DeepShortcutManager; 66import com.android.launcher3.shortcuts.ShortcutInfoCompat; 67import com.android.launcher3.shortcuts.ShortcutKey; 68import com.android.launcher3.util.ComponentKey; 69import com.android.launcher3.util.CursorIconInfo; 70import com.android.launcher3.util.FlagOp; 71import com.android.launcher3.util.GridOccupancy; 72import com.android.launcher3.util.LongArrayMap; 73import com.android.launcher3.util.ManagedProfileHeuristic; 74import com.android.launcher3.util.MultiHashMap; 75import com.android.launcher3.util.PackageManagerHelper; 76import com.android.launcher3.util.Preconditions; 77import com.android.launcher3.util.StringFilter; 78import com.android.launcher3.util.Thunk; 79import com.android.launcher3.util.ViewOnDrawExecutor; 80 81import java.lang.ref.WeakReference; 82import java.net.URISyntaxException; 83import java.security.InvalidParameterException; 84import java.util.ArrayList; 85import java.util.Arrays; 86import java.util.Collections; 87import java.util.Comparator; 88import java.util.HashMap; 89import java.util.HashSet; 90import java.util.Iterator; 91import java.util.List; 92import java.util.Map; 93import java.util.Map.Entry; 94import java.util.Set; 95import java.util.concurrent.Executor; 96 97/** 98 * Maintains in-memory state of the Launcher. It is expected that there should be only one 99 * LauncherModel object held in a static. Also provide APIs for updating the database state 100 * for the Launcher. 101 */ 102public class LauncherModel extends BroadcastReceiver 103 implements LauncherAppsCompat.OnAppsChangedCallbackCompat { 104 static final boolean DEBUG_LOADERS = false; 105 private static final boolean DEBUG_RECEIVER = false; 106 107 static final String TAG = "Launcher.Model"; 108 109 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 110 private static final long INVALID_SCREEN_ID = -1L; 111 112 @Thunk final LauncherAppState mApp; 113 @Thunk final Object mLock = new Object(); 114 @Thunk DeferredHandler mHandler = new DeferredHandler(); 115 @Thunk LoaderTask mLoaderTask; 116 @Thunk boolean mIsLoaderTaskRunning; 117 @Thunk boolean mHasLoaderCompletedOnce; 118 119 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 120 static { 121 sWorkerThread.start(); 122 } 123 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 124 125 // We start off with everything not loaded. After that, we assume that 126 // our monitoring of the package manager provides all updates and we never 127 // need to do a requery. These are only ever touched from the loader thread. 128 private boolean mWorkspaceLoaded; 129 private boolean mAllAppsLoaded; 130 private boolean mDeepShortcutsLoaded; 131 132 /** 133 * Set of runnables to be called on the background thread after the workspace binding 134 * is complete. 135 */ 136 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>(); 137 138 @Thunk WeakReference<Callbacks> mCallbacks; 139 140 // < only access in worker thread > 141 private final AllAppsList mBgAllAppsList; 142 // Entire list of widgets. 143 private final WidgetsModel mBgWidgetsModel; 144 145 // Maps all launcher activities to the id's of their shortcuts (if they have any). 146 private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>(); 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 = mDeepShortcutManager.hasHostPermission(); 155 if (hasShortcutHostPermission != mHasShortcutHostPermission) { 156 mApp.reloadWorkspace(); 157 } 158 } 159 } 160 }; 161 162 // The lock that must be acquired before referencing any static bg data structures. Unlike 163 // other locks, this one can generally be held long-term because we never expect any of these 164 // static data structures to be referenced outside of the worker thread except on the first 165 // load after configuration change. 166 static final Object sBgLock = new Object(); 167 168 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by 169 // LauncherModel to their ids 170 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>(); 171 172 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts 173 // created by LauncherModel that are directly on the home screen (however, no widgets or 174 // shortcuts within folders). 175 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>(); 176 177 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() 178 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = 179 new ArrayList<LauncherAppWidgetInfo>(); 180 181 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() 182 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>(); 183 184 // sBgWorkspaceScreens is the ordered set of workspace screens. 185 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>(); 186 187 // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of 188 // times it is pinned. 189 static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>(); 190 191 // sPendingPackages is a set of packages which could be on sdcard and are not available yet 192 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages = 193 new HashMap<UserHandleCompat, HashSet<String>>(); 194 195 // </ only access in worker thread > 196 197 private IconCache mIconCache; 198 private DeepShortcutManager mDeepShortcutManager; 199 200 private final LauncherAppsCompat mLauncherApps; 201 private final UserManagerCompat mUserManager; 202 203 public interface Callbacks { 204 public boolean setLoadOnResume(); 205 public int getCurrentWorkspaceScreen(); 206 public void clearPendingBinds(); 207 public void startBinding(); 208 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 209 boolean forceAnimateIcons); 210 public void bindScreens(ArrayList<Long> orderedScreenIds); 211 public void finishFirstPageBind(ViewOnDrawExecutor executor); 212 public void finishBindingItems(); 213 public void bindAppWidget(LauncherAppWidgetInfo info); 214 public void bindAllApplications(ArrayList<AppInfo> apps); 215 public void bindAppsAdded(ArrayList<Long> newScreens, 216 ArrayList<ItemInfo> addNotAnimated, 217 ArrayList<ItemInfo> addAnimated, 218 ArrayList<AppInfo> addedApps); 219 public void bindAppsUpdated(ArrayList<AppInfo> apps); 220 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, 221 ArrayList<ShortcutInfo> removed, UserHandleCompat user); 222 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); 223 public void bindRestoreItemsChange(HashSet<ItemInfo> updates); 224 public void bindWorkspaceComponentsRemoved( 225 HashSet<String> packageNames, HashSet<ComponentName> components, 226 UserHandleCompat user); 227 public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); 228 public void notifyWidgetProvidersChanged(); 229 public void bindWidgetsModel(WidgetsModel model); 230 public void onPageBoundSynchronously(int page); 231 public void executeOnNextDraw(ViewOnDrawExecutor executor); 232 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap); 233 } 234 235 public interface ItemInfoFilter { 236 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn); 237 } 238 239 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter, 240 DeepShortcutManager deepShortcutManager) { 241 Context context = app.getContext(); 242 mApp = app; 243 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 244 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter); 245 mIconCache = iconCache; 246 mDeepShortcutManager = deepShortcutManager; 247 248 mLauncherApps = LauncherAppsCompat.getInstance(context); 249 mUserManager = UserManagerCompat.getInstance(context); 250 } 251 252 /** Runs the specified runnable immediately if called from the main thread, otherwise it is 253 * posted on the main thread handler. */ 254 private void runOnMainThread(Runnable r) { 255 if (sWorkerThread.getThreadId() == Process.myTid()) { 256 // If we are on the worker thread, post onto the main handler 257 mHandler.post(r); 258 } else { 259 r.run(); 260 } 261 } 262 263 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 264 * posted on the worker thread handler. */ 265 private static void runOnWorkerThread(Runnable r) { 266 if (sWorkerThread.getThreadId() == Process.myTid()) { 267 r.run(); 268 } else { 269 // If we are not on the worker thread, then post to the worker handler 270 sWorker.post(r); 271 } 272 } 273 274 public void setPackageState(final PackageInstallInfo installInfo) { 275 Runnable updateRunnable = new Runnable() { 276 277 @Override 278 public void run() { 279 synchronized (sBgLock) { 280 final HashSet<ItemInfo> updates = new HashSet<>(); 281 282 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { 283 // Ignore install success events as they are handled by Package add events. 284 return; 285 } 286 287 for (ItemInfo info : sBgItemsIdMap) { 288 if (info instanceof ShortcutInfo) { 289 ShortcutInfo si = (ShortcutInfo) info; 290 ComponentName cn = si.getTargetComponent(); 291 if (si.isPromise() && (cn != null) 292 && installInfo.packageName.equals(cn.getPackageName())) { 293 si.setInstallProgress(installInfo.progress); 294 295 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { 296 // Mark this info as broken. 297 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; 298 } 299 updates.add(si); 300 } 301 } 302 } 303 304 for (LauncherAppWidgetInfo widget : sBgAppWidgets) { 305 if (widget.providerName.getPackageName().equals(installInfo.packageName)) { 306 widget.installProgress = installInfo.progress; 307 updates.add(widget); 308 } 309 } 310 311 if (!updates.isEmpty()) { 312 // Push changes to the callback. 313 Runnable r = new Runnable() { 314 public void run() { 315 Callbacks callbacks = getCallback(); 316 if (callbacks != null) { 317 callbacks.bindRestoreItemsChange(updates); 318 } 319 } 320 }; 321 mHandler.post(r); 322 } 323 } 324 } 325 }; 326 runOnWorkerThread(updateRunnable); 327 } 328 329 /** 330 * Updates the icons and label of all pending icons for the provided package name. 331 */ 332 public void updateSessionDisplayInfo(final String packageName) { 333 Runnable updateRunnable = new Runnable() { 334 335 @Override 336 public void run() { 337 synchronized (sBgLock) { 338 ArrayList<ShortcutInfo> updates = new ArrayList<>(); 339 UserHandleCompat user = UserHandleCompat.myUserHandle(); 340 341 for (ItemInfo info : sBgItemsIdMap) { 342 if (info instanceof ShortcutInfo) { 343 ShortcutInfo si = (ShortcutInfo) info; 344 ComponentName cn = si.getTargetComponent(); 345 if (si.isPromise() && (cn != null) 346 && packageName.equals(cn.getPackageName())) { 347 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { 348 // For auto install apps update the icon as well as label. 349 mIconCache.getTitleAndIcon(si, 350 si.promisedIntent, user, 351 si.shouldUseLowResIcon()); 352 } else { 353 // Only update the icon for restored apps. 354 si.updateIcon(mIconCache); 355 } 356 updates.add(si); 357 } 358 } 359 } 360 361 bindUpdatedShortcuts(updates, user); 362 } 363 } 364 }; 365 runOnWorkerThread(updateRunnable); 366 } 367 368 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) { 369 final Callbacks callbacks = getCallback(); 370 371 if (allAppsApps == null) { 372 throw new RuntimeException("allAppsApps must not be null"); 373 } 374 if (allAppsApps.isEmpty()) { 375 return; 376 } 377 378 // Process the newly added applications and add them to the database first 379 Runnable r = new Runnable() { 380 public void run() { 381 runOnMainThread(new Runnable() { 382 public void run() { 383 Callbacks cb = getCallback(); 384 if (callbacks == cb && cb != null) { 385 callbacks.bindAppsAdded(null, null, null, allAppsApps); 386 } 387 } 388 }); 389 } 390 }; 391 runOnWorkerThread(r); 392 } 393 394 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos, 395 int[] xy, int spanX, int spanY) { 396 LauncherAppState app = LauncherAppState.getInstance(); 397 InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 398 399 GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows); 400 if (occupiedPos != null) { 401 for (ItemInfo r : occupiedPos) { 402 occupied.markCells(r, true); 403 } 404 } 405 return occupied.findVacantCell(xy, spanX, spanY); 406 } 407 408 /** 409 * Find a position on the screen for the given size or adds a new screen. 410 * @return screenId and the coordinates for the item. 411 */ 412 @Thunk Pair<Long, int[]> findSpaceForItem( 413 Context context, 414 ArrayList<Long> workspaceScreens, 415 ArrayList<Long> addedWorkspaceScreensFinal, 416 int spanX, int spanY) { 417 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); 418 419 // Use sBgItemsIdMap as all the items are already loaded. 420 assertWorkspaceLoaded(); 421 synchronized (sBgLock) { 422 for (ItemInfo info : sBgItemsIdMap) { 423 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 424 ArrayList<ItemInfo> items = screenItems.get(info.screenId); 425 if (items == null) { 426 items = new ArrayList<>(); 427 screenItems.put(info.screenId, items); 428 } 429 items.add(info); 430 } 431 } 432 } 433 434 // Find appropriate space for the item. 435 long screenId = 0; 436 int[] cordinates = new int[2]; 437 boolean found = false; 438 439 int screenCount = workspaceScreens.size(); 440 // First check the preferred screen. 441 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1; 442 if (preferredScreenIndex < screenCount) { 443 screenId = workspaceScreens.get(preferredScreenIndex); 444 found = findNextAvailableIconSpaceInScreen( 445 screenItems.get(screenId), cordinates, spanX, spanY); 446 } 447 448 if (!found) { 449 // Search on any of the screens starting from the first screen. 450 for (int screen = 1; screen < screenCount; screen++) { 451 screenId = workspaceScreens.get(screen); 452 if (findNextAvailableIconSpaceInScreen( 453 screenItems.get(screenId), cordinates, spanX, spanY)) { 454 // We found a space for it 455 found = true; 456 break; 457 } 458 } 459 } 460 461 if (!found) { 462 // Still no position found. Add a new screen to the end. 463 screenId = LauncherSettings.Settings.call(context.getContentResolver(), 464 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) 465 .getLong(LauncherSettings.Settings.EXTRA_VALUE); 466 467 // Save the screen id for binding in the workspace 468 workspaceScreens.add(screenId); 469 addedWorkspaceScreensFinal.add(screenId); 470 471 // If we still can't find an empty space, then God help us all!!! 472 if (!findNextAvailableIconSpaceInScreen( 473 screenItems.get(screenId), cordinates, spanX, spanY)) { 474 throw new RuntimeException("Can't find space to add the item"); 475 } 476 } 477 return Pair.create(screenId, cordinates); 478 } 479 480 /** 481 * Adds the provided items to the workspace. 482 */ 483 public void addAndBindAddedWorkspaceItems(final Context context, 484 final ArrayList<? extends ItemInfo> workspaceApps) { 485 final Callbacks callbacks = getCallback(); 486 if (workspaceApps.isEmpty()) { 487 return; 488 } 489 // Process the newly added applications and add them to the database first 490 Runnable r = new Runnable() { 491 public void run() { 492 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>(); 493 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>(); 494 495 // Get the list of workspace screens. We need to append to this list and 496 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been 497 // called. 498 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context); 499 synchronized(sBgLock) { 500 for (ItemInfo item : workspaceApps) { 501 if (item instanceof ShortcutInfo) { 502 // Short-circuit this logic if the icon exists somewhere on the workspace 503 if (shortcutExists(context, item.getIntent(), item.user)) { 504 continue; 505 } 506 } 507 508 // Find appropriate space for the item. 509 Pair<Long, int[]> coords = findSpaceForItem(context, 510 workspaceScreens, addedWorkspaceScreensFinal, 1, 1); 511 long screenId = coords.first; 512 int[] cordinates = coords.second; 513 514 ItemInfo itemInfo; 515 if (item instanceof ShortcutInfo || item instanceof FolderInfo) { 516 itemInfo = item; 517 } else if (item instanceof AppInfo) { 518 itemInfo = ((AppInfo) item).makeShortcut(); 519 } else { 520 throw new RuntimeException("Unexpected info type"); 521 } 522 523 // Add the shortcut to the db 524 addItemToDatabase(context, itemInfo, 525 LauncherSettings.Favorites.CONTAINER_DESKTOP, 526 screenId, cordinates[0], cordinates[1]); 527 // Save the ShortcutInfo for binding in the workspace 528 addedShortcutsFinal.add(itemInfo); 529 } 530 } 531 532 // Update the workspace screens 533 updateWorkspaceScreenOrder(context, workspaceScreens); 534 535 if (!addedShortcutsFinal.isEmpty()) { 536 runOnMainThread(new Runnable() { 537 public void run() { 538 Callbacks cb = getCallback(); 539 if (callbacks == cb && cb != null) { 540 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>(); 541 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>(); 542 if (!addedShortcutsFinal.isEmpty()) { 543 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1); 544 long lastScreenId = info.screenId; 545 for (ItemInfo i : addedShortcutsFinal) { 546 if (i.screenId == lastScreenId) { 547 addAnimated.add(i); 548 } else { 549 addNotAnimated.add(i); 550 } 551 } 552 } 553 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, 554 addNotAnimated, addAnimated, null); 555 } 556 } 557 }); 558 } 559 } 560 }; 561 runOnWorkerThread(r); 562 } 563 564 /** 565 * Adds an item to the DB if it was not created previously, or move it to a new 566 * <container, screen, cellX, cellY> 567 */ 568 public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 569 long screenId, int cellX, int cellY) { 570 if (item.container == ItemInfo.NO_ID) { 571 // From all apps 572 addItemToDatabase(context, item, container, screenId, cellX, cellY); 573 } else { 574 // From somewhere else 575 moveItemInDatabase(context, item, container, screenId, cellX, cellY); 576 } 577 } 578 579 static void checkItemInfoLocked( 580 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { 581 ItemInfo modelItem = sBgItemsIdMap.get(itemId); 582 if (modelItem != null && item != modelItem) { 583 // check all the data is consistent 584 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { 585 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; 586 ShortcutInfo shortcut = (ShortcutInfo) item; 587 if (modelShortcut.title.toString().equals(shortcut.title.toString()) && 588 modelShortcut.intent.filterEquals(shortcut.intent) && 589 modelShortcut.id == shortcut.id && 590 modelShortcut.itemType == shortcut.itemType && 591 modelShortcut.container == shortcut.container && 592 modelShortcut.screenId == shortcut.screenId && 593 modelShortcut.cellX == shortcut.cellX && 594 modelShortcut.cellY == shortcut.cellY && 595 modelShortcut.spanX == shortcut.spanX && 596 modelShortcut.spanY == shortcut.spanY) { 597 // For all intents and purposes, this is the same object 598 return; 599 } 600 } 601 602 // the modelItem needs to match up perfectly with item if our model is 603 // to be consistent with the database-- for now, just require 604 // modelItem == item or the equality check above 605 String msg = "item: " + ((item != null) ? item.toString() : "null") + 606 "modelItem: " + 607 ((modelItem != null) ? modelItem.toString() : "null") + 608 "Error: ItemInfo passed to checkItemInfo doesn't match original"; 609 RuntimeException e = new RuntimeException(msg); 610 if (stackTrace != null) { 611 e.setStackTrace(stackTrace); 612 } 613 throw e; 614 } 615 } 616 617 static void checkItemInfo(final ItemInfo item) { 618 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 619 final long itemId = item.id; 620 Runnable r = new Runnable() { 621 public void run() { 622 synchronized (sBgLock) { 623 checkItemInfoLocked(itemId, item, stackTrace); 624 } 625 } 626 }; 627 runOnWorkerThread(r); 628 } 629 630 static void updateItemInDatabaseHelper(Context context, final ContentValues values, 631 final ItemInfo item, final String callingFunction) { 632 final long itemId = item.id; 633 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId); 634 final ContentResolver cr = context.getContentResolver(); 635 636 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 637 Runnable r = new Runnable() { 638 public void run() { 639 cr.update(uri, values, null, null); 640 updateItemArrays(item, itemId, stackTrace); 641 } 642 }; 643 runOnWorkerThread(r); 644 } 645 646 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, 647 final ArrayList<ItemInfo> items, final String callingFunction) { 648 final ContentResolver cr = context.getContentResolver(); 649 650 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 651 Runnable r = new Runnable() { 652 public void run() { 653 ArrayList<ContentProviderOperation> ops = 654 new ArrayList<ContentProviderOperation>(); 655 int count = items.size(); 656 for (int i = 0; i < count; i++) { 657 ItemInfo item = items.get(i); 658 final long itemId = item.id; 659 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId); 660 ContentValues values = valuesList.get(i); 661 662 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 663 updateItemArrays(item, itemId, stackTrace); 664 665 } 666 try { 667 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 668 } catch (Exception e) { 669 e.printStackTrace(); 670 } 671 } 672 }; 673 runOnWorkerThread(r); 674 } 675 676 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) { 677 // Lock on mBgLock *after* the db operation 678 synchronized (sBgLock) { 679 checkItemInfoLocked(itemId, item, stackTrace); 680 681 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 682 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 683 // Item is in a folder, make sure this folder exists 684 if (!sBgFolders.containsKey(item.container)) { 685 // An items container is being set to a that of an item which is not in 686 // the list of Folders. 687 String msg = "item: " + item + " container being set to: " + 688 item.container + ", not in the list of folders"; 689 Log.e(TAG, msg); 690 } 691 } 692 693 // Items are added/removed from the corresponding FolderInfo elsewhere, such 694 // as in Workspace.onDrop. Here, we just add/remove them from the list of items 695 // that are on the desktop, as appropriate 696 ItemInfo modelItem = sBgItemsIdMap.get(itemId); 697 if (modelItem != null && 698 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 699 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) { 700 switch (modelItem.itemType) { 701 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 702 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 703 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 704 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 705 if (!sBgWorkspaceItems.contains(modelItem)) { 706 sBgWorkspaceItems.add(modelItem); 707 } 708 break; 709 default: 710 break; 711 } 712 } else { 713 sBgWorkspaceItems.remove(modelItem); 714 } 715 } 716 } 717 718 /** 719 * Move an item in the DB to a new <container, screen, cellX, cellY> 720 */ 721 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container, 722 final long screenId, final int cellX, final int cellY) { 723 item.container = container; 724 item.cellX = cellX; 725 item.cellY = cellY; 726 727 // We store hotseat items in canonical form which is this orientation invariant position 728 // in the hotseat 729 if (context instanceof Launcher && screenId < 0 && 730 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 731 item.screenId = Launcher.getLauncher(context).getHotseat() 732 .getOrderInHotseat(cellX, cellY); 733 } else { 734 item.screenId = screenId; 735 } 736 737 final ContentValues values = new ContentValues(); 738 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 739 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 740 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 741 values.put(LauncherSettings.Favorites.RANK, item.rank); 742 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 743 744 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); 745 } 746 747 /** 748 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the 749 * cellX, cellY have already been updated on the ItemInfos. 750 */ 751 public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, 752 final long container, final int screen) { 753 754 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>(); 755 int count = items.size(); 756 757 for (int i = 0; i < count; i++) { 758 ItemInfo item = items.get(i); 759 item.container = container; 760 761 // We store hotseat items in canonical form which is this orientation invariant position 762 // in the hotseat 763 if (context instanceof Launcher && screen < 0 && 764 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 765 item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX, 766 item.cellY); 767 } else { 768 item.screenId = screen; 769 } 770 771 final ContentValues values = new ContentValues(); 772 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 773 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 774 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 775 values.put(LauncherSettings.Favorites.RANK, item.rank); 776 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 777 778 contentValues.add(values); 779 } 780 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase"); 781 } 782 783 /** 784 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY> 785 */ 786 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, 787 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) { 788 item.container = container; 789 item.cellX = cellX; 790 item.cellY = cellY; 791 item.spanX = spanX; 792 item.spanY = spanY; 793 794 // We store hotseat items in canonical form which is this orientation invariant position 795 // in the hotseat 796 if (context instanceof Launcher && screenId < 0 && 797 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 798 item.screenId = Launcher.getLauncher(context).getHotseat() 799 .getOrderInHotseat(cellX, cellY); 800 } else { 801 item.screenId = screenId; 802 } 803 804 final ContentValues values = new ContentValues(); 805 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 806 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 807 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 808 values.put(LauncherSettings.Favorites.RANK, item.rank); 809 values.put(LauncherSettings.Favorites.SPANX, item.spanX); 810 values.put(LauncherSettings.Favorites.SPANY, item.spanY); 811 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 812 813 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase"); 814 } 815 816 /** 817 * Update an item to the database in a specified container. 818 */ 819 public static void updateItemInDatabase(Context context, final ItemInfo item) { 820 final ContentValues values = new ContentValues(); 821 item.onAddToDatabase(context, values); 822 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); 823 } 824 825 private void assertWorkspaceLoaded() { 826 if (ProviderConfig.IS_DOGFOOD_BUILD) { 827 synchronized (mLock) { 828 if (!mHasLoaderCompletedOnce || 829 (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) { 830 throw new RuntimeException("Trying to add shortcut while loader is running"); 831 } 832 } 833 } 834 } 835 836 /** 837 * Returns true if the shortcuts already exists on the workspace. This must be called after 838 * the workspace has been loaded. We identify a shortcut by its intent. 839 */ 840 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { 841 assertWorkspaceLoaded(); 842 final String intentWithPkg, intentWithoutPkg; 843 if (intent.getComponent() != null) { 844 // If component is not null, an intent with null package will produce 845 // the same result and should also be a match. 846 String packageName = intent.getComponent().getPackageName(); 847 if (intent.getPackage() != null) { 848 intentWithPkg = intent.toUri(0); 849 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); 850 } else { 851 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0); 852 intentWithoutPkg = intent.toUri(0); 853 } 854 } else { 855 intentWithPkg = intent.toUri(0); 856 intentWithoutPkg = intent.toUri(0); 857 } 858 859 synchronized (sBgLock) { 860 for (ItemInfo item : sBgItemsIdMap) { 861 if (item instanceof ShortcutInfo) { 862 ShortcutInfo info = (ShortcutInfo) item; 863 Intent targetIntent = info.promisedIntent == null 864 ? info.intent : info.promisedIntent; 865 if (targetIntent != null && info.user.equals(user)) { 866 Intent copyIntent = new Intent(targetIntent); 867 copyIntent.setSourceBounds(intent.getSourceBounds()); 868 String s = copyIntent.toUri(0); 869 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { 870 return true; 871 } 872 } 873 } 874 } 875 } 876 return false; 877 } 878 879 /** 880 * Add an item to the database in a specified container. Sets the container, screen, cellX and 881 * cellY fields of the item. Also assigns an ID to the item. 882 */ 883 public static void addItemToDatabase(Context context, final ItemInfo item, final long container, 884 final long screenId, final int cellX, final int cellY) { 885 item.container = container; 886 item.cellX = cellX; 887 item.cellY = cellY; 888 // We store hotseat items in canonical form which is this orientation invariant position 889 // in the hotseat 890 if (context instanceof Launcher && screenId < 0 && 891 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 892 item.screenId = Launcher.getLauncher(context).getHotseat() 893 .getOrderInHotseat(cellX, cellY); 894 } else { 895 item.screenId = screenId; 896 } 897 898 final ContentValues values = new ContentValues(); 899 final ContentResolver cr = context.getContentResolver(); 900 item.onAddToDatabase(context, values); 901 902 item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) 903 .getLong(LauncherSettings.Settings.EXTRA_VALUE); 904 905 values.put(LauncherSettings.Favorites._ID, item.id); 906 907 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 908 Runnable r = new Runnable() { 909 public void run() { 910 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values); 911 912 // Lock on mBgLock *after* the db operation 913 synchronized (sBgLock) { 914 checkItemInfoLocked(item.id, item, stackTrace); 915 sBgItemsIdMap.put(item.id, item); 916 switch (item.itemType) { 917 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 918 sBgFolders.put(item.id, (FolderInfo) item); 919 // Fall through 920 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 921 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 922 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 923 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 924 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 925 sBgWorkspaceItems.add(item); 926 } else { 927 if (!sBgFolders.containsKey(item.container)) { 928 // Adding an item to a folder that doesn't exist. 929 String msg = "adding item: " + item + " to a folder that " + 930 " doesn't exist"; 931 Log.e(TAG, msg); 932 } 933 } 934 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 935 incrementPinnedShortcutCount( 936 ShortcutKey.fromShortcutInfo((ShortcutInfo) item), 937 true /* shouldPin */); 938 } 939 break; 940 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 941 sBgAppWidgets.add((LauncherAppWidgetInfo) item); 942 break; 943 } 944 } 945 } 946 }; 947 runOnWorkerThread(r); 948 } 949 950 private static ArrayList<ItemInfo> getItemsByPackageName( 951 final String pn, final UserHandleCompat user) { 952 ItemInfoFilter filter = new ItemInfoFilter() { 953 @Override 954 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { 955 return cn.getPackageName().equals(pn) && info.user.equals(user); 956 } 957 }; 958 return filterItemInfos(sBgItemsIdMap, filter); 959 } 960 961 /** 962 * Removes all the items from the database corresponding to the specified package. 963 */ 964 static void deletePackageFromDatabase(Context context, final String pn, 965 final UserHandleCompat user) { 966 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user)); 967 } 968 969 /** 970 * Removes the specified item from the database 971 */ 972 public static void deleteItemFromDatabase(Context context, final ItemInfo item) { 973 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); 974 items.add(item); 975 deleteItemsFromDatabase(context, items); 976 } 977 978 /** 979 * Removes the specified items from the database 980 */ 981 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) { 982 final ContentResolver cr = context.getContentResolver(); 983 Runnable r = new Runnable() { 984 public void run() { 985 for (ItemInfo item : items) { 986 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id); 987 cr.delete(uri, null, null); 988 989 // Lock on mBgLock *after* the db operation 990 synchronized (sBgLock) { 991 switch (item.itemType) { 992 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 993 sBgFolders.remove(item.id); 994 for (ItemInfo info: sBgItemsIdMap) { 995 if (info.container == item.id) { 996 // We are deleting a folder which still contains items that 997 // think they are contained by that folder. 998 String msg = "deleting a folder (" + item + ") which still " + 999 "contains items (" + info + ")"; 1000 Log.e(TAG, msg); 1001 } 1002 } 1003 sBgWorkspaceItems.remove(item); 1004 break; 1005 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 1006 decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo( 1007 (ShortcutInfo) item)); 1008 // Fall through. 1009 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1010 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1011 sBgWorkspaceItems.remove(item); 1012 break; 1013 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1014 sBgAppWidgets.remove((LauncherAppWidgetInfo) item); 1015 break; 1016 } 1017 sBgItemsIdMap.remove(item.id); 1018 } 1019 } 1020 } 1021 }; 1022 runOnWorkerThread(r); 1023 } 1024 1025 /** 1026 * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0. 1027 */ 1028 private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) { 1029 synchronized (sBgLock) { 1030 MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut); 1031 if (count == null || --count.value == 0) { 1032 LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut); 1033 } 1034 } 1035 } 1036 1037 /** 1038 * Increment the count for the given shortcut, pinning it if the count becomes 1. 1039 * 1040 * As an optimization, the caller can pass shouldPin == false to avoid 1041 * unnecessary RPC's if the shortcut is already pinned. 1042 */ 1043 private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) { 1044 synchronized (sBgLock) { 1045 MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut); 1046 if (count == null) { 1047 count = new MutableInt(1); 1048 sBgPinnedShortcutCounts.put(pinnedShortcut, count); 1049 } else { 1050 count.value++; 1051 } 1052 if (shouldPin && count.value == 1) { 1053 LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut); 1054 } 1055 } 1056 } 1057 1058 /** 1059 * Update the order of the workspace screens in the database. The array list contains 1060 * a list of screen ids in the order that they should appear. 1061 */ 1062 public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { 1063 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); 1064 final ContentResolver cr = context.getContentResolver(); 1065 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 1066 1067 // Remove any negative screen ids -- these aren't persisted 1068 Iterator<Long> iter = screensCopy.iterator(); 1069 while (iter.hasNext()) { 1070 long id = iter.next(); 1071 if (id < 0) { 1072 iter.remove(); 1073 } 1074 } 1075 1076 Runnable r = new Runnable() { 1077 @Override 1078 public void run() { 1079 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1080 // Clear the table 1081 ops.add(ContentProviderOperation.newDelete(uri).build()); 1082 int count = screensCopy.size(); 1083 for (int i = 0; i < count; i++) { 1084 ContentValues v = new ContentValues(); 1085 long screenId = screensCopy.get(i); 1086 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 1087 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 1088 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); 1089 } 1090 1091 try { 1092 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 1093 } catch (Exception ex) { 1094 throw new RuntimeException(ex); 1095 } 1096 1097 synchronized (sBgLock) { 1098 sBgWorkspaceScreens.clear(); 1099 sBgWorkspaceScreens.addAll(screensCopy); 1100 } 1101 } 1102 }; 1103 runOnWorkerThread(r); 1104 } 1105 1106 /** 1107 * Remove the specified folder and all its contents from the database. 1108 */ 1109 public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) { 1110 final ContentResolver cr = context.getContentResolver(); 1111 1112 Runnable r = new Runnable() { 1113 public void run() { 1114 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null); 1115 // Lock on mBgLock *after* the db operation 1116 synchronized (sBgLock) { 1117 sBgItemsIdMap.remove(info.id); 1118 sBgFolders.remove(info.id); 1119 sBgWorkspaceItems.remove(info); 1120 } 1121 1122 cr.delete(LauncherSettings.Favorites.CONTENT_URI, 1123 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 1124 // Lock on mBgLock *after* the db operation 1125 synchronized (sBgLock) { 1126 for (ItemInfo childInfo : info.contents) { 1127 sBgItemsIdMap.remove(childInfo.id); 1128 } 1129 } 1130 } 1131 }; 1132 runOnWorkerThread(r); 1133 } 1134 1135 /** 1136 * Set this as the current Launcher activity object for the loader. 1137 */ 1138 public void initialize(Callbacks callbacks) { 1139 synchronized (mLock) { 1140 Preconditions.assertUIThread(); 1141 // Remove any queued UI runnables 1142 mHandler.cancelAll(); 1143 mCallbacks = new WeakReference<>(callbacks); 1144 } 1145 } 1146 1147 @Override 1148 public void onPackageChanged(String packageName, UserHandleCompat user) { 1149 int op = PackageUpdatedTask.OP_UPDATE; 1150 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, 1151 user)); 1152 } 1153 1154 @Override 1155 public void onPackageRemoved(String packageName, UserHandleCompat user) { 1156 int op = PackageUpdatedTask.OP_REMOVE; 1157 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, 1158 user)); 1159 } 1160 1161 @Override 1162 public void onPackageAdded(String packageName, UserHandleCompat user) { 1163 int op = PackageUpdatedTask.OP_ADD; 1164 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, 1165 user)); 1166 } 1167 1168 @Override 1169 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user, 1170 boolean replacing) { 1171 enqueueItemUpdatedTask( 1172 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user)); 1173 } 1174 1175 @Override 1176 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, 1177 boolean replacing) { 1178 if (!replacing) { 1179 enqueueItemUpdatedTask(new PackageUpdatedTask( 1180 PackageUpdatedTask.OP_UNAVAILABLE, packageNames, 1181 user)); 1182 } 1183 } 1184 1185 @Override 1186 public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { 1187 enqueueItemUpdatedTask(new PackageUpdatedTask( 1188 PackageUpdatedTask.OP_SUSPEND, packageNames, 1189 user)); 1190 } 1191 1192 @Override 1193 public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { 1194 enqueueItemUpdatedTask(new PackageUpdatedTask( 1195 PackageUpdatedTask.OP_UNSUSPEND, packageNames, 1196 user)); 1197 } 1198 1199 @Override 1200 public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, 1201 UserHandleCompat user) { 1202 enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); 1203 } 1204 1205 public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, 1206 UserHandleCompat user) { 1207 enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); 1208 } 1209 1210 /** 1211 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 1212 * ACTION_PACKAGE_CHANGED. 1213 */ 1214 @Override 1215 public void onReceive(Context context, Intent intent) { 1216 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); 1217 1218 final String action = intent.getAction(); 1219 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 1220 // If we have changed locale we need to clear out the labels in all apps/workspace. 1221 forceReload(); 1222 } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) 1223 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { 1224 UserManagerCompat.getInstance(context).enableAndResetCache(); 1225 forceReload(); 1226 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 1227 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 1228 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 1229 UserHandleCompat user = UserHandleCompat.fromIntent(intent); 1230 if (user != null) { 1231 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 1232 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { 1233 enqueueItemUpdatedTask(new PackageUpdatedTask( 1234 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, 1235 new String[0], user)); 1236 } 1237 1238 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so 1239 // we need to run the state change task again. 1240 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 1241 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 1242 enqueueItemUpdatedTask(new UserLockStateChangedTask(user)); 1243 } 1244 } 1245 } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { 1246 ExtractionUtils.startColorExtractionServiceIfNecessary(context); 1247 } 1248 } 1249 1250 void forceReload() { 1251 resetLoadedState(true, true); 1252 1253 // Do this here because if the launcher activity is running it will be restarted. 1254 // If it's not running startLoaderFromBackground will merely tell it that it needs 1255 // to reload. 1256 startLoaderFromBackground(); 1257 } 1258 1259 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { 1260 synchronized (mLock) { 1261 // Stop any existing loaders first, so they don't set mAllAppsLoaded or 1262 // mWorkspaceLoaded to true later 1263 stopLoaderLocked(); 1264 if (resetAllAppsLoaded) mAllAppsLoaded = false; 1265 if (resetWorkspaceLoaded) mWorkspaceLoaded = false; 1266 // Always reset deep shortcuts loaded. 1267 // TODO: why? 1268 mDeepShortcutsLoaded = false; 1269 } 1270 } 1271 1272 /** 1273 * When the launcher is in the background, it's possible for it to miss paired 1274 * configuration changes. So whenever we trigger the loader from the background 1275 * tell the launcher that it needs to re-run the loader when it comes back instead 1276 * of doing it now. 1277 */ 1278 public void startLoaderFromBackground() { 1279 Callbacks callbacks = getCallback(); 1280 if (callbacks != null) { 1281 // Only actually run the loader if they're not paused. 1282 if (!callbacks.setLoadOnResume()) { 1283 startLoader(callbacks.getCurrentWorkspaceScreen()); 1284 } 1285 } 1286 } 1287 1288 /** 1289 * If there is already a loader task running, tell it to stop. 1290 */ 1291 private void stopLoaderLocked() { 1292 LoaderTask oldTask = mLoaderTask; 1293 if (oldTask != null) { 1294 oldTask.stopLocked(); 1295 } 1296 } 1297 1298 public boolean isCurrentCallbacks(Callbacks callbacks) { 1299 return (mCallbacks != null && mCallbacks.get() == callbacks); 1300 } 1301 1302 /** 1303 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 1304 * @return true if the page could be bound synchronously. 1305 */ 1306 public boolean startLoader(int synchronousBindPage) { 1307 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 1308 InstallShortcutReceiver.enableInstallQueue(); 1309 synchronized (mLock) { 1310 // Don't bother to start the thread if we know it's not going to do anything 1311 if (mCallbacks != null && mCallbacks.get() != null) { 1312 final Callbacks oldCallbacks = mCallbacks.get(); 1313 // Clear any pending bind-runnables from the synchronized load process. 1314 runOnMainThread(new Runnable() { 1315 public void run() { 1316 oldCallbacks.clearPendingBinds(); 1317 } 1318 }); 1319 1320 // If there is already one running, tell it to stop. 1321 stopLoaderLocked(); 1322 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage); 1323 // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind. 1324 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded 1325 && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) { 1326 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 1327 return true; 1328 } else { 1329 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 1330 sWorker.post(mLoaderTask); 1331 } 1332 } 1333 } 1334 return false; 1335 } 1336 1337 public void stopLoader() { 1338 synchronized (mLock) { 1339 if (mLoaderTask != null) { 1340 mLoaderTask.stopLocked(); 1341 } 1342 } 1343 } 1344 1345 /** 1346 * Loads the workspace screen ids in an ordered list. 1347 */ 1348 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) { 1349 final ContentResolver contentResolver = context.getContentResolver(); 1350 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 1351 1352 // Get screens ordered by rank. 1353 return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query( 1354 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); 1355 } 1356 1357 /** 1358 * Runnable for the thread that loads the contents of the launcher: 1359 * - workspace icons 1360 * - widgets 1361 * - all apps icons 1362 * - deep shortcuts within apps 1363 */ 1364 private class LoaderTask implements Runnable { 1365 private Context mContext; 1366 private int mPageToBindFirst; 1367 1368 @Thunk boolean mIsLoadingAndBindingWorkspace; 1369 private boolean mStopped; 1370 @Thunk boolean mLoadAndBindStepFinished; 1371 1372 LoaderTask(Context context, int pageToBindFirst) { 1373 mContext = context; 1374 mPageToBindFirst = pageToBindFirst; 1375 } 1376 1377 private void loadAndBindWorkspace() { 1378 mIsLoadingAndBindingWorkspace = true; 1379 1380 // Load the workspace 1381 if (DEBUG_LOADERS) { 1382 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 1383 } 1384 1385 if (!mWorkspaceLoaded) { 1386 loadWorkspace(); 1387 synchronized (LoaderTask.this) { 1388 if (mStopped) { 1389 return; 1390 } 1391 mWorkspaceLoaded = true; 1392 } 1393 } 1394 1395 // Bind the workspace 1396 bindWorkspace(mPageToBindFirst); 1397 } 1398 1399 private void waitForIdle() { 1400 // Wait until the either we're stopped or the other threads are done. 1401 // This way we don't start loading all apps until the workspace has settled 1402 // down. 1403 synchronized (LoaderTask.this) { 1404 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1405 1406 mHandler.postIdle(new Runnable() { 1407 public void run() { 1408 synchronized (LoaderTask.this) { 1409 mLoadAndBindStepFinished = true; 1410 if (DEBUG_LOADERS) { 1411 Log.d(TAG, "done with previous binding step"); 1412 } 1413 LoaderTask.this.notify(); 1414 } 1415 } 1416 }); 1417 1418 while (!mStopped && !mLoadAndBindStepFinished) { 1419 try { 1420 // Just in case mFlushingWorkerThread changes but we aren't woken up, 1421 // wait no longer than 1sec at a time 1422 this.wait(1000); 1423 } catch (InterruptedException ex) { 1424 // Ignore 1425 } 1426 } 1427 if (DEBUG_LOADERS) { 1428 Log.d(TAG, "waited " 1429 + (SystemClock.uptimeMillis()-workspaceWaitTime) 1430 + "ms for previous step to finish binding"); 1431 } 1432 } 1433 } 1434 1435 void runBindSynchronousPage(int synchronousBindPage) { 1436 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) { 1437 // Ensure that we have a valid page index to load synchronously 1438 throw new RuntimeException("Should not call runBindSynchronousPage() without " + 1439 "valid page index"); 1440 } 1441 if (!mAllAppsLoaded || !mWorkspaceLoaded) { 1442 // Ensure that we don't try and bind a specified page when the pages have not been 1443 // loaded already (we should load everything asynchronously in that case) 1444 throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); 1445 } 1446 synchronized (mLock) { 1447 if (mIsLoaderTaskRunning) { 1448 // Ensure that we are never running the background loading at this point since 1449 // we also touch the background collections 1450 throw new RuntimeException("Error! Background loading is already running"); 1451 } 1452 } 1453 1454 // XXX: Throw an exception if we are already loading (since we touch the worker thread 1455 // data structures, we can't allow any other thread to touch that data, but because 1456 // this call is synchronous, we can get away with not locking). 1457 1458 // The LauncherModel is static in the LauncherAppState and mHandler may have queued 1459 // operations from the previous activity. We need to ensure that all queued operations 1460 // are executed before any synchronous binding work is done. 1461 mHandler.flush(); 1462 1463 // Divide the set of loaded items into those that we are binding synchronously, and 1464 // everything else that is to be bound normally (asynchronously). 1465 bindWorkspace(synchronousBindPage); 1466 // XXX: For now, continue posting the binding of AllApps as there are other issues that 1467 // arise from that. 1468 onlyBindAllApps(); 1469 1470 bindDeepShortcuts(); 1471 } 1472 1473 public void run() { 1474 synchronized (mLock) { 1475 if (mStopped) { 1476 return; 1477 } 1478 mIsLoaderTaskRunning = true; 1479 } 1480 // Optimize for end-user experience: if the Launcher is up and // running with the 1481 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 1482 // workspace first (default). 1483 keep_running: { 1484 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 1485 loadAndBindWorkspace(); 1486 1487 if (mStopped) { 1488 break keep_running; 1489 } 1490 1491 waitForIdle(); 1492 1493 // second step 1494 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 1495 loadAndBindAllApps(); 1496 1497 waitForIdle(); 1498 1499 // third step 1500 if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts"); 1501 loadAndBindDeepShortcuts(); 1502 } 1503 1504 // Clear out this reference, otherwise we end up holding it until all of the 1505 // callback runnables are done. 1506 mContext = null; 1507 1508 synchronized (mLock) { 1509 // If we are still the last one to be scheduled, remove ourselves. 1510 if (mLoaderTask == this) { 1511 mLoaderTask = null; 1512 } 1513 mIsLoaderTaskRunning = false; 1514 mHasLoaderCompletedOnce = true; 1515 } 1516 } 1517 1518 public void stopLocked() { 1519 synchronized (LoaderTask.this) { 1520 mStopped = true; 1521 this.notify(); 1522 } 1523 } 1524 1525 /** 1526 * Gets the callbacks object. If we've been stopped, or if the launcher object 1527 * has somehow been garbage collected, return null instead. Pass in the Callbacks 1528 * object that was around when the deferred message was scheduled, and if there's 1529 * a new Callbacks object around then also return null. This will save us from 1530 * calling onto it with data that will be ignored. 1531 */ 1532 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 1533 synchronized (mLock) { 1534 if (mStopped) { 1535 return null; 1536 } 1537 1538 if (mCallbacks == null) { 1539 return null; 1540 } 1541 1542 final Callbacks callbacks = mCallbacks.get(); 1543 if (callbacks != oldCallbacks) { 1544 return null; 1545 } 1546 if (callbacks == null) { 1547 Log.w(TAG, "no mCallbacks"); 1548 return null; 1549 } 1550 1551 return callbacks; 1552 } 1553 } 1554 1555 // check & update map of what's occupied; used to discard overlapping/invalid items 1556 private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item, 1557 ArrayList<Long> workspaceScreens) { 1558 LauncherAppState app = LauncherAppState.getInstance(); 1559 InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 1560 1561 long containerIndex = item.screenId; 1562 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1563 // Return early if we detect that an item is under the hotseat button 1564 if (!FeatureFlags.NO_ALL_APPS_ICON && 1565 profile.isAllAppsButtonRank((int) item.screenId)) { 1566 Log.e(TAG, "Error loading shortcut into hotseat " + item 1567 + " into position (" + item.screenId + ":" + item.cellX + "," 1568 + item.cellY + ") occupied by all apps"); 1569 return false; 1570 } 1571 1572 final GridOccupancy hotseatOccupancy = 1573 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT); 1574 1575 if (item.screenId >= profile.numHotseatIcons) { 1576 Log.e(TAG, "Error loading shortcut " + item 1577 + " into hotseat position " + item.screenId 1578 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1) 1579 + ")"); 1580 return false; 1581 } 1582 1583 if (hotseatOccupancy != null) { 1584 if (hotseatOccupancy.cells[(int) item.screenId][0]) { 1585 Log.e(TAG, "Error loading shortcut into hotseat " + item 1586 + " into position (" + item.screenId + ":" + item.cellX + "," 1587 + item.cellY + ") already occupied"); 1588 return false; 1589 } else { 1590 hotseatOccupancy.cells[(int) item.screenId][0] = true; 1591 return true; 1592 } 1593 } else { 1594 final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1); 1595 occupancy.cells[(int) item.screenId][0] = true; 1596 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy); 1597 return true; 1598 } 1599 } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1600 if (!workspaceScreens.contains((Long) item.screenId)) { 1601 // The item has an invalid screen id. 1602 return false; 1603 } 1604 } else { 1605 // Skip further checking if it is not the hotseat or workspace container 1606 return true; 1607 } 1608 1609 final int countX = profile.numColumns; 1610 final int countY = profile.numRows; 1611 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1612 item.cellX < 0 || item.cellY < 0 || 1613 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) { 1614 Log.e(TAG, "Error loading shortcut " + item 1615 + " into cell (" + containerIndex + "-" + item.screenId + ":" 1616 + item.cellX + "," + item.cellY 1617 + ") out of screen bounds ( " + countX + "x" + countY + ")"); 1618 return false; 1619 } 1620 1621 if (!occupied.containsKey(item.screenId)) { 1622 GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1); 1623 if (item.screenId == Workspace.FIRST_SCREEN_ID) { 1624 // Mark the first row as occupied (if the feature is enabled) 1625 // in order to account for the QSB. 1626 screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN); 1627 } 1628 occupied.put(item.screenId, screen); 1629 } 1630 final GridOccupancy occupancy = occupied.get(item.screenId); 1631 1632 // Check if any workspace icons overlap with each other 1633 if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) { 1634 occupancy.markCells(item, true); 1635 return true; 1636 } else { 1637 Log.e(TAG, "Error loading shortcut " + item 1638 + " into cell (" + containerIndex + "-" + item.screenId + ":" 1639 + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY 1640 + ") already occupied"); 1641 return false; 1642 } 1643 } 1644 1645 /** Clears all the sBg data structures */ 1646 private void clearSBgDataStructures() { 1647 synchronized (sBgLock) { 1648 sBgWorkspaceItems.clear(); 1649 sBgAppWidgets.clear(); 1650 sBgFolders.clear(); 1651 sBgItemsIdMap.clear(); 1652 sBgWorkspaceScreens.clear(); 1653 sBgPinnedShortcutCounts.clear(); 1654 } 1655 } 1656 1657 private void loadWorkspace() { 1658 if (LauncherAppState.PROFILE_STARTUP) { 1659 Trace.beginSection("Loading Workspace"); 1660 } 1661 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1662 1663 final Context context = mContext; 1664 final ContentResolver contentResolver = context.getContentResolver(); 1665 final PackageManager manager = context.getPackageManager(); 1666 final boolean isSafeMode = manager.isSafeMode(); 1667 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 1668 final boolean isSdCardReady = Utilities.isBootCompleted(); 1669 1670 LauncherAppState app = LauncherAppState.getInstance(); 1671 InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 1672 int countX = profile.numColumns; 1673 int countY = profile.numRows; 1674 1675 boolean clearDb = false; 1676 try { 1677 ImportDataTask.performImportIfPossible(context); 1678 } catch (Exception e) { 1679 // Migration failed. Clear workspace. 1680 clearDb = true; 1681 } 1682 1683 if (!clearDb && GridSizeMigrationTask.ENABLED && 1684 !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { 1685 // Migration failed. Clear workspace. 1686 clearDb = true; 1687 } 1688 1689 if (clearDb) { 1690 Log.d(TAG, "loadWorkspace: resetting launcher database"); 1691 LauncherSettings.Settings.call(contentResolver, 1692 LauncherSettings.Settings.METHOD_DELETE_DB); 1693 } 1694 1695 Log.d(TAG, "loadWorkspace: loading default favorites"); 1696 LauncherSettings.Settings.call(contentResolver, 1697 LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); 1698 1699 synchronized (sBgLock) { 1700 clearSBgDataStructures(); 1701 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat 1702 .getInstance(mContext).updateAndGetActiveSessionCache(); 1703 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); 1704 1705 final ArrayList<Long> itemsToRemove = new ArrayList<>(); 1706 final ArrayList<Long> restoredRows = new ArrayList<>(); 1707 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); 1708 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI; 1709 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri); 1710 final Cursor c = contentResolver.query(contentUri, null, null, null, null); 1711 1712 // +1 for the hotseat (it can be larger than the workspace) 1713 // Load workspace in reverse order to ensure that latest items are loaded first (and 1714 // before any earlier duplicates) 1715 final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>(); 1716 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; 1717 1718 try { 1719 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1720 final int intentIndex = c.getColumnIndexOrThrow 1721 (LauncherSettings.Favorites.INTENT); 1722 final int containerIndex = c.getColumnIndexOrThrow( 1723 LauncherSettings.Favorites.CONTAINER); 1724 final int itemTypeIndex = c.getColumnIndexOrThrow( 1725 LauncherSettings.Favorites.ITEM_TYPE); 1726 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 1727 LauncherSettings.Favorites.APPWIDGET_ID); 1728 final int appWidgetProviderIndex = c.getColumnIndexOrThrow( 1729 LauncherSettings.Favorites.APPWIDGET_PROVIDER); 1730 final int screenIndex = c.getColumnIndexOrThrow( 1731 LauncherSettings.Favorites.SCREEN); 1732 final int cellXIndex = c.getColumnIndexOrThrow 1733 (LauncherSettings.Favorites.CELLX); 1734 final int cellYIndex = c.getColumnIndexOrThrow 1735 (LauncherSettings.Favorites.CELLY); 1736 final int spanXIndex = c.getColumnIndexOrThrow 1737 (LauncherSettings.Favorites.SPANX); 1738 final int spanYIndex = c.getColumnIndexOrThrow( 1739 LauncherSettings.Favorites.SPANY); 1740 final int rankIndex = c.getColumnIndexOrThrow( 1741 LauncherSettings.Favorites.RANK); 1742 final int restoredIndex = c.getColumnIndexOrThrow( 1743 LauncherSettings.Favorites.RESTORED); 1744 final int profileIdIndex = c.getColumnIndexOrThrow( 1745 LauncherSettings.Favorites.PROFILE_ID); 1746 final int optionsIndex = c.getColumnIndexOrThrow( 1747 LauncherSettings.Favorites.OPTIONS); 1748 final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c); 1749 1750 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>(); 1751 final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); 1752 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); 1753 for (UserHandleCompat user : mUserManager.getUserProfiles()) { 1754 long serialNo = mUserManager.getSerialNumberForUser(user); 1755 allUsers.put(serialNo, user); 1756 quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); 1757 1758 boolean userUnlocked = mUserManager.isUserUnlocked(user); 1759 1760 // We can only query for shortcuts when the user is unlocked. 1761 if (userUnlocked) { 1762 List<ShortcutInfoCompat> pinnedShortcuts = 1763 mDeepShortcutManager.queryForPinnedShortcuts(null, user); 1764 if (mDeepShortcutManager.wasLastCallSuccess()) { 1765 for (ShortcutInfoCompat shortcut : pinnedShortcuts) { 1766 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), 1767 shortcut); 1768 } 1769 } else { 1770 // Shortcut manager can fail due to some race condition when the 1771 // lock state changes too frequently. For the purpose of the loading 1772 // shortcuts, consider the user is still locked. 1773 userUnlocked = false; 1774 } 1775 } 1776 unlockedUsers.put(serialNo, userUnlocked); 1777 } 1778 1779 ShortcutInfo info; 1780 String intentDescription; 1781 LauncherAppWidgetInfo appWidgetInfo; 1782 int container; 1783 long id; 1784 long serialNumber; 1785 Intent intent; 1786 UserHandleCompat user; 1787 String targetPackage; 1788 1789 while (!mStopped && c.moveToNext()) { 1790 try { 1791 int itemType = c.getInt(itemTypeIndex); 1792 boolean restored = 0 != c.getInt(restoredIndex); 1793 boolean allowMissingTarget = false; 1794 container = c.getInt(containerIndex); 1795 1796 switch (itemType) { 1797 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1798 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1799 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 1800 id = c.getLong(idIndex); 1801 intentDescription = c.getString(intentIndex); 1802 serialNumber = c.getInt(profileIdIndex); 1803 user = allUsers.get(serialNumber); 1804 int promiseType = c.getInt(restoredIndex); 1805 int disabledState = 0; 1806 boolean itemReplaced = false; 1807 targetPackage = null; 1808 if (user == null) { 1809 // User has been deleted remove the item. 1810 itemsToRemove.add(id); 1811 continue; 1812 } 1813 try { 1814 intent = Intent.parseUri(intentDescription, 0); 1815 ComponentName cn = intent.getComponent(); 1816 if (cn != null && cn.getPackageName() != null) { 1817 boolean validPkg = launcherApps.isPackageEnabledForProfile( 1818 cn.getPackageName(), user); 1819 boolean validComponent = validPkg && 1820 launcherApps.isActivityEnabledForProfile(cn, user); 1821 if (validPkg) { 1822 targetPackage = cn.getPackageName(); 1823 } 1824 1825 if (validComponent) { 1826 if (restored) { 1827 // no special handling necessary for this item 1828 restoredRows.add(id); 1829 restored = false; 1830 } 1831 if (quietMode.get(serialNumber)) { 1832 disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER; 1833 } 1834 } else if (validPkg) { 1835 intent = null; 1836 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { 1837 // We allow auto install apps to have their intent 1838 // updated after an install. 1839 intent = manager.getLaunchIntentForPackage( 1840 cn.getPackageName()); 1841 if (intent != null) { 1842 ContentValues values = new ContentValues(); 1843 values.put(LauncherSettings.Favorites.INTENT, 1844 intent.toUri(0)); 1845 updateItem(id, values); 1846 } 1847 } 1848 1849 if (intent == null) { 1850 // The app is installed but the component is no 1851 // longer available. 1852 FileLog.d(TAG, "Invalid component removed: " + cn); 1853 itemsToRemove.add(id); 1854 continue; 1855 } else { 1856 // no special handling necessary for this item 1857 restoredRows.add(id); 1858 restored = false; 1859 } 1860 } else if (restored) { 1861 // Package is not yet available but might be 1862 // installed later. 1863 FileLog.d(TAG, "package not yet restored: " + cn); 1864 1865 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) { 1866 // Restore has started once. 1867 } else if (installingPkgs.containsKey(cn.getPackageName())) { 1868 // App restore has started. Update the flag 1869 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED; 1870 ContentValues values = new ContentValues(); 1871 values.put(LauncherSettings.Favorites.RESTORED, 1872 promiseType); 1873 updateItem(id, values); 1874 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) { 1875 // This is a common app. Try to replace this. 1876 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType); 1877 CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context); 1878 if (parser.findDefaultApp()) { 1879 // Default app found. Replace it. 1880 intent = parser.parsedIntent; 1881 cn = intent.getComponent(); 1882 ContentValues values = parser.parsedValues; 1883 values.put(LauncherSettings.Favorites.RESTORED, 0); 1884 updateItem(id, values); 1885 restored = false; 1886 itemReplaced = true; 1887 1888 } else { 1889 FileLog.d(TAG, "Unrestored package removed: " + cn); 1890 itemsToRemove.add(id); 1891 continue; 1892 } 1893 } else { 1894 FileLog.d(TAG, "Unrestored package removed: " + cn); 1895 itemsToRemove.add(id); 1896 continue; 1897 } 1898 } else if (PackageManagerHelper.isAppOnSdcard( 1899 manager, cn.getPackageName())) { 1900 // Package is present but not available. 1901 allowMissingTarget = true; 1902 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; 1903 } else if (!isSdCardReady) { 1904 // SdCard is not ready yet. Package might get available, 1905 // once it is ready. 1906 Log.d(TAG, "Invalid package: " + cn + " (check again later)"); 1907 HashSet<String> pkgs = sPendingPackages.get(user); 1908 if (pkgs == null) { 1909 pkgs = new HashSet<String>(); 1910 sPendingPackages.put(user, pkgs); 1911 } 1912 pkgs.add(cn.getPackageName()); 1913 allowMissingTarget = true; 1914 // Add the icon on the workspace anyway. 1915 1916 } else { 1917 // Do not wait for external media load anymore. 1918 // Log the invalid package, and remove it 1919 FileLog.d(TAG, "Invalid package removed: " + cn); 1920 itemsToRemove.add(id); 1921 continue; 1922 } 1923 } else if (cn == null) { 1924 // For shortcuts with no component, keep them as they are 1925 restoredRows.add(id); 1926 restored = false; 1927 } 1928 } catch (URISyntaxException e) { 1929 FileLog.d(TAG, "Invalid uri: " + intentDescription); 1930 itemsToRemove.add(id); 1931 continue; 1932 } 1933 1934 boolean useLowResIcon = container >= 0 && 1935 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; 1936 1937 if (itemReplaced) { 1938 if (user.equals(UserHandleCompat.myUserHandle())) { 1939 info = getAppShortcutInfo(intent, user, null, 1940 cursorIconInfo, false, useLowResIcon); 1941 } else { 1942 // Don't replace items for other profiles. 1943 itemsToRemove.add(id); 1944 continue; 1945 } 1946 } else if (restored) { 1947 if (user.equals(UserHandleCompat.myUserHandle())) { 1948 info = getRestoredItemInfo(c, intent, 1949 promiseType, itemType, cursorIconInfo); 1950 intent = getRestoredItemIntent(c, context, intent); 1951 } else { 1952 // Don't restore items for other profiles. 1953 itemsToRemove.add(id); 1954 continue; 1955 } 1956 } else if (itemType == 1957 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1958 info = getAppShortcutInfo(intent, user, c, 1959 cursorIconInfo, allowMissingTarget, useLowResIcon); 1960 } else if (itemType == 1961 LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 1962 1963 ShortcutKey key = ShortcutKey.fromIntent(intent, user); 1964 if (unlockedUsers.get(serialNumber)) { 1965 ShortcutInfoCompat pinnedShortcut = 1966 shortcutKeyToPinnedShortcuts.get(key); 1967 if (pinnedShortcut == null) { 1968 // The shortcut is no longer valid. 1969 itemsToRemove.add(id); 1970 continue; 1971 } 1972 info = new ShortcutInfo(pinnedShortcut, context); 1973 intent = info.intent; 1974 } else { 1975 // Create a shortcut info in disabled mode for now. 1976 info = new ShortcutInfo(); 1977 info.user = user; 1978 info.itemType = itemType; 1979 loadInfoFromCursor(info, c, cursorIconInfo); 1980 1981 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; 1982 } 1983 incrementPinnedShortcutCount(key, false /* shouldPin */); 1984 } else { // item type == ITEM_TYPE_SHORTCUT 1985 info = getShortcutInfo(c, cursorIconInfo); 1986 1987 // Shortcuts are only available on the primary profile 1988 if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) { 1989 disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; 1990 } 1991 1992 // App shortcuts that used to be automatically added to Launcher 1993 // didn't always have the correct intent flags set, so do that 1994 // here 1995 if (intent.getAction() != null && 1996 intent.getCategories() != null && 1997 intent.getAction().equals(Intent.ACTION_MAIN) && 1998 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 1999 intent.addFlags( 2000 Intent.FLAG_ACTIVITY_NEW_TASK | 2001 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 2002 } 2003 } 2004 2005 if (info != null) { 2006 info.id = id; 2007 info.intent = intent; 2008 info.container = container; 2009 info.screenId = c.getInt(screenIndex); 2010 info.cellX = c.getInt(cellXIndex); 2011 info.cellY = c.getInt(cellYIndex); 2012 info.rank = c.getInt(rankIndex); 2013 info.spanX = 1; 2014 info.spanY = 1; 2015 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber); 2016 if (info.promisedIntent != null) { 2017 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber); 2018 } 2019 info.isDisabled |= disabledState; 2020 if (isSafeMode && !Utilities.isSystemApp(context, intent)) { 2021 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; 2022 } 2023 2024 // check & update map of what's occupied 2025 if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) { 2026 itemsToRemove.add(id); 2027 break; 2028 } 2029 2030 if (restored) { 2031 ComponentName cn = info.getTargetComponent(); 2032 if (cn != null) { 2033 Integer progress = installingPkgs.get(cn.getPackageName()); 2034 if (progress != null) { 2035 info.setInstallProgress(progress); 2036 } else { 2037 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; 2038 } 2039 } 2040 } 2041 2042 switch (container) { 2043 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 2044 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 2045 sBgWorkspaceItems.add(info); 2046 break; 2047 default: 2048 // Item is in a user folder 2049 FolderInfo folderInfo = 2050 findOrMakeFolder(sBgFolders, container); 2051 folderInfo.add(info, false); 2052 break; 2053 } 2054 sBgItemsIdMap.put(info.id, info); 2055 } else { 2056 throw new RuntimeException("Unexpected null ShortcutInfo"); 2057 } 2058 break; 2059 2060 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 2061 id = c.getLong(idIndex); 2062 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); 2063 2064 // Do not trim the folder label, as is was set by the user. 2065 folderInfo.title = c.getString(cursorIconInfo.titleIndex); 2066 folderInfo.id = id; 2067 folderInfo.container = container; 2068 folderInfo.screenId = c.getInt(screenIndex); 2069 folderInfo.cellX = c.getInt(cellXIndex); 2070 folderInfo.cellY = c.getInt(cellYIndex); 2071 folderInfo.spanX = 1; 2072 folderInfo.spanY = 1; 2073 folderInfo.options = c.getInt(optionsIndex); 2074 2075 // check & update map of what's occupied 2076 if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) { 2077 itemsToRemove.add(id); 2078 break; 2079 } 2080 2081 switch (container) { 2082 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 2083 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 2084 sBgWorkspaceItems.add(folderInfo); 2085 break; 2086 } 2087 2088 if (restored) { 2089 // no special handling required for restored folders 2090 restoredRows.add(id); 2091 } 2092 2093 sBgItemsIdMap.put(folderInfo.id, folderInfo); 2094 sBgFolders.put(folderInfo.id, folderInfo); 2095 break; 2096 2097 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 2098 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 2099 // Read all Launcher-specific widget details 2100 boolean customWidget = itemType == 2101 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 2102 2103 int appWidgetId = c.getInt(appWidgetIdIndex); 2104 serialNumber = c.getLong(profileIdIndex); 2105 String savedProvider = c.getString(appWidgetProviderIndex); 2106 id = c.getLong(idIndex); 2107 user = allUsers.get(serialNumber); 2108 if (user == null) { 2109 itemsToRemove.add(id); 2110 continue; 2111 } 2112 2113 final ComponentName component = 2114 ComponentName.unflattenFromString(savedProvider); 2115 2116 final int restoreStatus = c.getInt(restoredIndex); 2117 final boolean isIdValid = (restoreStatus & 2118 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0; 2119 final boolean wasProviderReady = (restoreStatus & 2120 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0; 2121 2122 if (widgetProvidersMap == null) { 2123 widgetProvidersMap = AppWidgetManagerCompat 2124 .getInstance(mContext).getAllProvidersMap(); 2125 } 2126 final AppWidgetProviderInfo provider = widgetProvidersMap.get( 2127 new ComponentKey( 2128 ComponentName.unflattenFromString(savedProvider), 2129 user)); 2130 2131 final boolean isProviderReady = isValidProvider(provider); 2132 if (!isSafeMode && !customWidget && 2133 wasProviderReady && !isProviderReady) { 2134 FileLog.d(TAG, "Deleting widget that isn't installed anymore: " 2135 + provider); 2136 itemsToRemove.add(id); 2137 } else { 2138 if (isProviderReady) { 2139 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 2140 provider.provider); 2141 2142 // The provider is available. So the widget is either 2143 // available or not available. We do not need to track 2144 // any future restore updates. 2145 int status = restoreStatus & 2146 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 2147 if (!wasProviderReady) { 2148 // If provider was not previously ready, update the 2149 // status and UI flag. 2150 2151 // Id would be valid only if the widget restore broadcast was received. 2152 if (isIdValid) { 2153 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 2154 } else { 2155 status &= ~LauncherAppWidgetInfo 2156 .FLAG_PROVIDER_NOT_READY; 2157 } 2158 } 2159 appWidgetInfo.restoreStatus = status; 2160 } else { 2161 Log.v(TAG, "Widget restore pending id=" + id 2162 + " appWidgetId=" + appWidgetId 2163 + " status =" + restoreStatus); 2164 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 2165 component); 2166 appWidgetInfo.restoreStatus = restoreStatus; 2167 Integer installProgress = installingPkgs.get(component.getPackageName()); 2168 2169 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) { 2170 // Restore has started once. 2171 } else if (installProgress != null) { 2172 // App restore has started. Update the flag 2173 appWidgetInfo.restoreStatus |= 2174 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 2175 } else if (!isSafeMode) { 2176 FileLog.d(TAG, "Unrestored widget removed: " + component); 2177 itemsToRemove.add(id); 2178 continue; 2179 } 2180 2181 appWidgetInfo.installProgress = 2182 installProgress == null ? 0 : installProgress; 2183 } 2184 if (appWidgetInfo.hasRestoreFlag( 2185 LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) { 2186 intentDescription = c.getString(intentIndex); 2187 if (!TextUtils.isEmpty(intentDescription)) { 2188 appWidgetInfo.bindOptions = 2189 Intent.parseUri(intentDescription, 0); 2190 } 2191 } 2192 2193 appWidgetInfo.id = id; 2194 appWidgetInfo.screenId = c.getInt(screenIndex); 2195 appWidgetInfo.cellX = c.getInt(cellXIndex); 2196 appWidgetInfo.cellY = c.getInt(cellYIndex); 2197 appWidgetInfo.spanX = c.getInt(spanXIndex); 2198 appWidgetInfo.spanY = c.getInt(spanYIndex); 2199 appWidgetInfo.user = user; 2200 2201 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 2202 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2203 Log.e(TAG, "Widget found where container != " + 2204 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 2205 itemsToRemove.add(id); 2206 continue; 2207 } 2208 2209 appWidgetInfo.container = container; 2210 // check & update map of what's occupied 2211 if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) { 2212 itemsToRemove.add(id); 2213 break; 2214 } 2215 2216 if (!customWidget) { 2217 String providerName = 2218 appWidgetInfo.providerName.flattenToString(); 2219 if (!providerName.equals(savedProvider) || 2220 (appWidgetInfo.restoreStatus != restoreStatus)) { 2221 ContentValues values = new ContentValues(); 2222 values.put( 2223 LauncherSettings.Favorites.APPWIDGET_PROVIDER, 2224 providerName); 2225 values.put(LauncherSettings.Favorites.RESTORED, 2226 appWidgetInfo.restoreStatus); 2227 updateItem(id, values); 2228 } 2229 } 2230 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); 2231 sBgAppWidgets.add(appWidgetInfo); 2232 } 2233 break; 2234 } 2235 } catch (Exception e) { 2236 Log.e(TAG, "Desktop items loading interrupted", e); 2237 } 2238 } 2239 } finally { 2240 Utilities.closeSilently(c); 2241 } 2242 2243 // Break early if we've stopped loading 2244 if (mStopped) { 2245 clearSBgDataStructures(); 2246 return; 2247 } 2248 2249 if (itemsToRemove.size() > 0) { 2250 // Remove dead items 2251 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI, 2252 Utilities.createDbSelectionQuery( 2253 LauncherSettings.Favorites._ID, itemsToRemove), null); 2254 if (DEBUG_LOADERS) { 2255 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery( 2256 LauncherSettings.Favorites._ID, itemsToRemove)); 2257 } 2258 2259 // Remove any empty folder 2260 ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings 2261 .call(contentResolver, 2262 LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) 2263 .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); 2264 for (long folderId : deletedFolderIds) { 2265 sBgWorkspaceItems.remove(sBgFolders.get(folderId)); 2266 sBgFolders.remove(folderId); 2267 sBgItemsIdMap.remove(folderId); 2268 } 2269 } 2270 2271 // Unpin shortcuts that don't exist on the workspace. 2272 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) { 2273 MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key); 2274 if (numTimesPinned == null || numTimesPinned.value == 0) { 2275 // Shortcut is pinned but doesn't exist on the workspace; unpin it. 2276 mDeepShortcutManager.unpinShortcut(key); 2277 } 2278 } 2279 2280 // Sort all the folder items and make sure the first 3 items are high resolution. 2281 for (FolderInfo folder : sBgFolders) { 2282 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); 2283 int pos = 0; 2284 for (ShortcutInfo info : folder.contents) { 2285 if (info.usingLowResIcon) { 2286 info.updateIcon(mIconCache, false); 2287 } 2288 pos ++; 2289 if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { 2290 break; 2291 } 2292 } 2293 } 2294 2295 if (restoredRows.size() > 0) { 2296 // Update restored items that no longer require special handling 2297 ContentValues values = new ContentValues(); 2298 values.put(LauncherSettings.Favorites.RESTORED, 0); 2299 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values, 2300 Utilities.createDbSelectionQuery( 2301 LauncherSettings.Favorites._ID, restoredRows), null); 2302 } 2303 2304 if (!isSdCardReady && !sPendingPackages.isEmpty()) { 2305 context.registerReceiver(new AppsAvailabilityCheck(), 2306 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), 2307 null, sWorker); 2308 } 2309 2310 // Remove any empty screens 2311 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); 2312 for (ItemInfo item: sBgItemsIdMap) { 2313 long screenId = item.screenId; 2314 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2315 unusedScreens.contains(screenId)) { 2316 unusedScreens.remove(screenId); 2317 } 2318 } 2319 2320 // If there are any empty screens remove them, and update. 2321 if (unusedScreens.size() != 0) { 2322 sBgWorkspaceScreens.removeAll(unusedScreens); 2323 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); 2324 } 2325 2326 if (DEBUG_LOADERS) { 2327 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 2328 Log.d(TAG, "workspace layout: "); 2329 int nScreens = occupied.size(); 2330 for (int y = 0; y < countY; y++) { 2331 String line = ""; 2332 2333 for (int i = 0; i < nScreens; i++) { 2334 long screenId = occupied.keyAt(i); 2335 if (screenId > 0) { 2336 line += " | "; 2337 } 2338 } 2339 Log.d(TAG, "[ " + line + " ]"); 2340 } 2341 } 2342 } 2343 if (LauncherAppState.PROFILE_STARTUP) { 2344 Trace.endSection(); 2345 } 2346 } 2347 2348 /** 2349 * Partially updates the item without any notification. Must be called on the worker thread. 2350 */ 2351 private void updateItem(long itemId, ContentValues update) { 2352 mContext.getContentResolver().update( 2353 LauncherSettings.Favorites.CONTENT_URI, 2354 update, 2355 BaseColumns._ID + "= ?", 2356 new String[]{Long.toString(itemId)}); 2357 } 2358 2359 /** Filters the set of items who are directly or indirectly (via another container) on the 2360 * specified screen. */ 2361 private void filterCurrentWorkspaceItems(long currentScreenId, 2362 ArrayList<ItemInfo> allWorkspaceItems, 2363 ArrayList<ItemInfo> currentScreenItems, 2364 ArrayList<ItemInfo> otherScreenItems) { 2365 // Purge any null ItemInfos 2366 Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); 2367 while (iter.hasNext()) { 2368 ItemInfo i = iter.next(); 2369 if (i == null) { 2370 iter.remove(); 2371 } 2372 } 2373 2374 // Order the set of items by their containers first, this allows use to walk through the 2375 // list sequentially, build up a list of containers that are in the specified screen, 2376 // as well as all items in those containers. 2377 Set<Long> itemsOnScreen = new HashSet<Long>(); 2378 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 2379 @Override 2380 public int compare(ItemInfo lhs, ItemInfo rhs) { 2381 return Utilities.longCompare(lhs.container, rhs.container); 2382 } 2383 }); 2384 for (ItemInfo info : allWorkspaceItems) { 2385 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 2386 if (info.screenId == currentScreenId) { 2387 currentScreenItems.add(info); 2388 itemsOnScreen.add(info.id); 2389 } else { 2390 otherScreenItems.add(info); 2391 } 2392 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2393 currentScreenItems.add(info); 2394 itemsOnScreen.add(info.id); 2395 } else { 2396 if (itemsOnScreen.contains(info.container)) { 2397 currentScreenItems.add(info); 2398 itemsOnScreen.add(info.id); 2399 } else { 2400 otherScreenItems.add(info); 2401 } 2402 } 2403 } 2404 } 2405 2406 /** Filters the set of widgets which are on the specified screen. */ 2407 private void filterCurrentAppWidgets(long currentScreenId, 2408 ArrayList<LauncherAppWidgetInfo> appWidgets, 2409 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, 2410 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { 2411 2412 for (LauncherAppWidgetInfo widget : appWidgets) { 2413 if (widget == null) continue; 2414 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2415 widget.screenId == currentScreenId) { 2416 currentScreenWidgets.add(widget); 2417 } else { 2418 otherScreenWidgets.add(widget); 2419 } 2420 } 2421 } 2422 2423 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 2424 * right) */ 2425 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 2426 final LauncherAppState app = LauncherAppState.getInstance(); 2427 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); 2428 final int screenCols = profile.numColumns; 2429 final int screenCellCount = profile.numColumns * profile.numRows; 2430 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 2431 @Override 2432 public int compare(ItemInfo lhs, ItemInfo rhs) { 2433 if (lhs.container == rhs.container) { 2434 // Within containers, order by their spatial position in that container 2435 switch ((int) lhs.container) { 2436 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 2437 long lr = (lhs.screenId * screenCellCount + 2438 lhs.cellY * screenCols + lhs.cellX); 2439 long rr = (rhs.screenId * screenCellCount + 2440 rhs.cellY * screenCols + rhs.cellX); 2441 return Utilities.longCompare(lr, rr); 2442 } 2443 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 2444 // We currently use the screen id as the rank 2445 return Utilities.longCompare(lhs.screenId, rhs.screenId); 2446 } 2447 default: 2448 if (ProviderConfig.IS_DOGFOOD_BUILD) { 2449 throw new RuntimeException("Unexpected container type when " + 2450 "sorting workspace items."); 2451 } 2452 return 0; 2453 } 2454 } else { 2455 // Between containers, order by hotseat, desktop 2456 return Utilities.longCompare(lhs.container, rhs.container); 2457 } 2458 } 2459 }); 2460 } 2461 2462 private void bindWorkspaceScreens(final Callbacks oldCallbacks, 2463 final ArrayList<Long> orderedScreens) { 2464 final Runnable r = new Runnable() { 2465 @Override 2466 public void run() { 2467 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2468 if (callbacks != null) { 2469 callbacks.bindScreens(orderedScreens); 2470 } 2471 } 2472 }; 2473 runOnMainThread(r); 2474 } 2475 2476 private void bindWorkspaceItems(final Callbacks oldCallbacks, 2477 final ArrayList<ItemInfo> workspaceItems, 2478 final ArrayList<LauncherAppWidgetInfo> appWidgets, 2479 final Executor executor) { 2480 2481 // Bind the workspace items 2482 int N = workspaceItems.size(); 2483 for (int i = 0; i < N; i += ITEMS_CHUNK) { 2484 final int start = i; 2485 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 2486 final Runnable r = new Runnable() { 2487 @Override 2488 public void run() { 2489 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2490 if (callbacks != null) { 2491 callbacks.bindItems(workspaceItems, start, start+chunkSize, 2492 false); 2493 } 2494 } 2495 }; 2496 executor.execute(r); 2497 } 2498 2499 // Bind the widgets, one at a time 2500 N = appWidgets.size(); 2501 for (int i = 0; i < N; i++) { 2502 final LauncherAppWidgetInfo widget = appWidgets.get(i); 2503 final Runnable r = new Runnable() { 2504 public void run() { 2505 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2506 if (callbacks != null) { 2507 callbacks.bindAppWidget(widget); 2508 } 2509 } 2510 }; 2511 executor.execute(r); 2512 } 2513 } 2514 2515 /** 2516 * Binds all loaded data to actual views on the main thread. 2517 */ 2518 private void bindWorkspace(int synchronizeBindPage) { 2519 final long t = SystemClock.uptimeMillis(); 2520 Runnable r; 2521 2522 // Don't use these two variables in any of the callback runnables. 2523 // Otherwise we hold a reference to them. 2524 final Callbacks oldCallbacks = mCallbacks.get(); 2525 if (oldCallbacks == null) { 2526 // This launcher has exited and nobody bothered to tell us. Just bail. 2527 Log.w(TAG, "LoaderTask running with no launcher"); 2528 return; 2529 } 2530 2531 // Save a copy of all the bg-thread collections 2532 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 2533 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 2534 ArrayList<Long> orderedScreenIds = new ArrayList<>(); 2535 2536 synchronized (sBgLock) { 2537 workspaceItems.addAll(sBgWorkspaceItems); 2538 appWidgets.addAll(sBgAppWidgets); 2539 orderedScreenIds.addAll(sBgWorkspaceScreens); 2540 } 2541 2542 final int currentScreen; 2543 { 2544 int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE 2545 ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen(); 2546 if (currScreen >= orderedScreenIds.size()) { 2547 // There may be no workspace screens (just hotseat items and an empty page). 2548 currScreen = PagedView.INVALID_RESTORE_PAGE; 2549 } 2550 currentScreen = currScreen; 2551 } 2552 final boolean validFirstPage = currentScreen >= 0; 2553 final long currentScreenId = 2554 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 2555 2556 // Separate the items that are on the current screen, and all the other remaining items 2557 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 2558 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 2559 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 2560 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 2561 2562 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 2563 otherWorkspaceItems); 2564 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, 2565 otherAppWidgets); 2566 sortWorkspaceItemsSpatially(currentWorkspaceItems); 2567 sortWorkspaceItemsSpatially(otherWorkspaceItems); 2568 2569 // Tell the workspace that we're about to start binding items 2570 r = new Runnable() { 2571 public void run() { 2572 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2573 if (callbacks != null) { 2574 callbacks.clearPendingBinds(); 2575 callbacks.startBinding(); 2576 } 2577 } 2578 }; 2579 runOnMainThread(r); 2580 2581 bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 2582 2583 Executor mainExecutor = new DeferredMainThreadExecutor(); 2584 // Load items on the current page. 2585 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); 2586 2587 // In case of validFirstPage, only bind the first screen, and defer binding the 2588 // remaining screens after first onDraw (and an optional the fade animation whichever 2589 // happens later). 2590 // This ensures that the first screen is immediately visible (eg. during rotation) 2591 // In case of !validFirstPage, bind all pages one after other. 2592 final Executor deferredExecutor = 2593 validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor; 2594 2595 mainExecutor.execute(new Runnable() { 2596 @Override 2597 public void run() { 2598 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2599 if (callbacks != null) { 2600 callbacks.finishFirstPageBind( 2601 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); 2602 } 2603 } 2604 }); 2605 2606 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor); 2607 2608 // Tell the workspace that we're done binding items 2609 r = new Runnable() { 2610 public void run() { 2611 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2612 if (callbacks != null) { 2613 callbacks.finishBindingItems(); 2614 } 2615 2616 mIsLoadingAndBindingWorkspace = false; 2617 2618 // Run all the bind complete runnables after workspace is bound. 2619 if (!mBindCompleteRunnables.isEmpty()) { 2620 synchronized (mBindCompleteRunnables) { 2621 for (final Runnable r : mBindCompleteRunnables) { 2622 runOnWorkerThread(r); 2623 } 2624 mBindCompleteRunnables.clear(); 2625 } 2626 } 2627 2628 // If we're profiling, ensure this is the last thing in the queue. 2629 if (DEBUG_LOADERS) { 2630 Log.d(TAG, "bound workspace in " 2631 + (SystemClock.uptimeMillis()-t) + "ms"); 2632 } 2633 2634 } 2635 }; 2636 deferredExecutor.execute(r); 2637 2638 if (validFirstPage) { 2639 r = new Runnable() { 2640 public void run() { 2641 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2642 if (callbacks != null) { 2643 // We are loading synchronously, which means, some of the pages will be 2644 // bound after first draw. Inform the callbacks that page binding is 2645 // not complete, and schedule the remaining pages. 2646 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 2647 callbacks.onPageBoundSynchronously(currentScreen); 2648 } 2649 callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 2650 } 2651 } 2652 }; 2653 runOnMainThread(r); 2654 } 2655 } 2656 2657 private void loadAndBindAllApps() { 2658 if (DEBUG_LOADERS) { 2659 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 2660 } 2661 if (!mAllAppsLoaded) { 2662 loadAllApps(); 2663 synchronized (LoaderTask.this) { 2664 if (mStopped) { 2665 return; 2666 } 2667 } 2668 updateIconCache(); 2669 synchronized (LoaderTask.this) { 2670 if (mStopped) { 2671 return; 2672 } 2673 mAllAppsLoaded = true; 2674 } 2675 } else { 2676 onlyBindAllApps(); 2677 } 2678 } 2679 2680 private void updateIconCache() { 2681 // Ignore packages which have a promise icon. 2682 HashSet<String> packagesToIgnore = new HashSet<>(); 2683 synchronized (sBgLock) { 2684 for (ItemInfo info : sBgItemsIdMap) { 2685 if (info instanceof ShortcutInfo) { 2686 ShortcutInfo si = (ShortcutInfo) info; 2687 if (si.isPromise() && si.getTargetComponent() != null) { 2688 packagesToIgnore.add(si.getTargetComponent().getPackageName()); 2689 } 2690 } else if (info instanceof LauncherAppWidgetInfo) { 2691 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; 2692 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 2693 packagesToIgnore.add(lawi.providerName.getPackageName()); 2694 } 2695 } 2696 } 2697 } 2698 mIconCache.updateDbIcons(packagesToIgnore); 2699 } 2700 2701 private void onlyBindAllApps() { 2702 final Callbacks oldCallbacks = mCallbacks.get(); 2703 if (oldCallbacks == null) { 2704 // This launcher has exited and nobody bothered to tell us. Just bail. 2705 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 2706 return; 2707 } 2708 2709 // shallow copy 2710 @SuppressWarnings("unchecked") 2711 final ArrayList<AppInfo> list 2712 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); 2713 Runnable r = new Runnable() { 2714 public void run() { 2715 final long t = SystemClock.uptimeMillis(); 2716 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2717 if (callbacks != null) { 2718 callbacks.bindAllApplications(list); 2719 } 2720 if (DEBUG_LOADERS) { 2721 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 2722 + (SystemClock.uptimeMillis() - t) + "ms"); 2723 } 2724 } 2725 }; 2726 runOnMainThread(r); 2727 } 2728 2729 private void loadAllApps() { 2730 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2731 2732 final Callbacks oldCallbacks = mCallbacks.get(); 2733 if (oldCallbacks == null) { 2734 // This launcher has exited and nobody bothered to tell us. Just bail. 2735 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); 2736 return; 2737 } 2738 2739 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles(); 2740 2741 // Clear the list of apps 2742 mBgAllAppsList.clear(); 2743 for (UserHandleCompat user : profiles) { 2744 // Query for the set of apps 2745 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2746 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user); 2747 if (DEBUG_LOADERS) { 2748 Log.d(TAG, "getActivityList took " 2749 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); 2750 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); 2751 } 2752 // Fail if we don't have any apps 2753 // TODO: Fix this. Only fail for the current user. 2754 if (apps == null || apps.isEmpty()) { 2755 return; 2756 } 2757 boolean quietMode = mUserManager.isQuietModeEnabled(user); 2758 // Create the ApplicationInfos 2759 for (int i = 0; i < apps.size(); i++) { 2760 LauncherActivityInfoCompat app = apps.get(i); 2761 // This builds the icon bitmaps. 2762 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode)); 2763 } 2764 2765 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); 2766 if (heuristic != null) { 2767 final Runnable r = new Runnable() { 2768 2769 @Override 2770 public void run() { 2771 heuristic.processUserApps(apps); 2772 } 2773 }; 2774 runOnMainThread(new Runnable() { 2775 2776 @Override 2777 public void run() { 2778 // Check isLoadingWorkspace on the UI thread, as it is updated on 2779 // the UI thread. 2780 if (mIsLoadingAndBindingWorkspace) { 2781 synchronized (mBindCompleteRunnables) { 2782 mBindCompleteRunnables.add(r); 2783 } 2784 } else { 2785 runOnWorkerThread(r); 2786 } 2787 } 2788 }); 2789 } 2790 } 2791 // Huh? Shouldn't this be inside the Runnable below? 2792 final ArrayList<AppInfo> added = mBgAllAppsList.added; 2793 mBgAllAppsList.added = new ArrayList<AppInfo>(); 2794 2795 // Post callback on main thread 2796 mHandler.post(new Runnable() { 2797 public void run() { 2798 2799 final long bindTime = SystemClock.uptimeMillis(); 2800 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2801 if (callbacks != null) { 2802 callbacks.bindAllApplications(added); 2803 if (DEBUG_LOADERS) { 2804 Log.d(TAG, "bound " + added.size() + " apps in " 2805 + (SystemClock.uptimeMillis() - bindTime) + "ms"); 2806 } 2807 } else { 2808 Log.i(TAG, "not binding apps: no Launcher activity"); 2809 } 2810 } 2811 }); 2812 // Cleanup any data stored for a deleted user. 2813 ManagedProfileHeuristic.processAllUsers(profiles, mContext); 2814 if (DEBUG_LOADERS) { 2815 Log.d(TAG, "Icons processed in " 2816 + (SystemClock.uptimeMillis() - loadTime) + "ms"); 2817 } 2818 } 2819 2820 private void loadAndBindDeepShortcuts() { 2821 if (DEBUG_LOADERS) { 2822 Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded); 2823 } 2824 if (!mDeepShortcutsLoaded) { 2825 mBgDeepShortcutMap.clear(); 2826 mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission(); 2827 if (mHasShortcutHostPermission) { 2828 for (UserHandleCompat user : mUserManager.getUserProfiles()) { 2829 if (mUserManager.isUserUnlocked(user)) { 2830 List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager 2831 .queryForAllShortcuts(user); 2832 updateDeepShortcutMap(null, user, shortcuts); 2833 } 2834 } 2835 } 2836 synchronized (LoaderTask.this) { 2837 if (mStopped) { 2838 return; 2839 } 2840 mDeepShortcutsLoaded = true; 2841 } 2842 } 2843 bindDeepShortcuts(); 2844 } 2845 2846 public void dumpState() { 2847 synchronized (sBgLock) { 2848 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 2849 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 2850 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 2851 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); 2852 } 2853 } 2854 } 2855 2856 /** 2857 * Clear all the shortcuts for the given package, and re-add the new shortcuts. 2858 */ 2859 private void updateDeepShortcutMap( 2860 String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) { 2861 if (packageName != null) { 2862 Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator(); 2863 while (keysIter.hasNext()) { 2864 ComponentKey next = keysIter.next(); 2865 if (next.componentName.getPackageName().equals(packageName) 2866 && next.user.equals(user)) { 2867 keysIter.remove(); 2868 } 2869 } 2870 } 2871 2872 // Now add the new shortcuts to the map. 2873 for (ShortcutInfoCompat shortcut : shortcuts) { 2874 boolean shouldShowInContainer = shortcut.isEnabled() 2875 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic()); 2876 if (shouldShowInContainer) { 2877 ComponentKey targetComponent 2878 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle()); 2879 mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId()); 2880 } 2881 } 2882 } 2883 2884 public void bindDeepShortcuts() { 2885 final MultiHashMap<ComponentKey, String> shortcutMapCopy = mBgDeepShortcutMap.clone(); 2886 Runnable r = new Runnable() { 2887 @Override 2888 public void run() { 2889 Callbacks callbacks = getCallback(); 2890 if (callbacks != null) { 2891 callbacks.bindDeepShortcutMap(shortcutMapCopy); 2892 } 2893 } 2894 }; 2895 runOnMainThread(r); 2896 } 2897 2898 /** 2899 * Refreshes the cached shortcuts if the shortcut permission has changed. 2900 * Current implementation simply reloads the workspace, but it can be optimized to 2901 * use partial updates similar to {@link UserManagerCompat} 2902 */ 2903 public void refreshShortcutsIfRequired() { 2904 if (Utilities.isNycMR1OrAbove()) { 2905 sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); 2906 sWorker.post(mShortcutPermissionCheckRunnable); 2907 } 2908 } 2909 2910 /** 2911 * Called when the icons for packages have been updated in the icon cache. 2912 */ 2913 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) { 2914 final Callbacks callbacks = getCallback(); 2915 final ArrayList<AppInfo> updatedApps = new ArrayList<>(); 2916 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); 2917 2918 // If any package icon has changed (app was updated while launcher was dead), 2919 // update the corresponding shortcuts. 2920 synchronized (sBgLock) { 2921 for (ItemInfo info : sBgItemsIdMap) { 2922 if (info instanceof ShortcutInfo && user.equals(info.user) 2923 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 2924 ShortcutInfo si = (ShortcutInfo) info; 2925 ComponentName cn = si.getTargetComponent(); 2926 if (cn != null && updatedPackages.contains(cn.getPackageName())) { 2927 si.updateIcon(mIconCache); 2928 updatedShortcuts.add(si); 2929 } 2930 } 2931 } 2932 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps); 2933 } 2934 2935 bindUpdatedShortcuts(updatedShortcuts, user); 2936 2937 if (!updatedApps.isEmpty()) { 2938 mHandler.post(new Runnable() { 2939 2940 public void run() { 2941 Callbacks cb = getCallback(); 2942 if (cb != null && callbacks == cb) { 2943 cb.bindAppsUpdated(updatedApps); 2944 } 2945 } 2946 }); 2947 } 2948 } 2949 2950 private void bindUpdatedShortcuts( 2951 ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) { 2952 bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user); 2953 } 2954 2955 private void bindUpdatedShortcuts( 2956 final ArrayList<ShortcutInfo> updatedShortcuts, 2957 final ArrayList<ShortcutInfo> removedShortcuts, 2958 final UserHandleCompat user) { 2959 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) { 2960 final Callbacks callbacks = getCallback(); 2961 mHandler.post(new Runnable() { 2962 2963 public void run() { 2964 Callbacks cb = getCallback(); 2965 if (cb != null && callbacks == cb) { 2966 cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user); 2967 } 2968 } 2969 }); 2970 } 2971 } 2972 2973 void enqueueItemUpdatedTask(Runnable task) { 2974 sWorker.post(task); 2975 } 2976 2977 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver { 2978 2979 @Override 2980 public void onReceive(Context context, Intent intent) { 2981 synchronized (sBgLock) { 2982 final LauncherAppsCompat launcherApps = LauncherAppsCompat 2983 .getInstance(mApp.getContext()); 2984 final PackageManager manager = context.getPackageManager(); 2985 final ArrayList<String> packagesRemoved = new ArrayList<String>(); 2986 final ArrayList<String> packagesUnavailable = new ArrayList<String>(); 2987 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) { 2988 UserHandleCompat user = entry.getKey(); 2989 packagesRemoved.clear(); 2990 packagesUnavailable.clear(); 2991 for (String pkg : entry.getValue()) { 2992 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) { 2993 if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) { 2994 packagesUnavailable.add(pkg); 2995 } else { 2996 packagesRemoved.add(pkg); 2997 } 2998 } 2999 } 3000 if (!packagesRemoved.isEmpty()) { 3001 enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE, 3002 packagesRemoved.toArray(new String[packagesRemoved.size()]), user)); 3003 } 3004 if (!packagesUnavailable.isEmpty()) { 3005 enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE, 3006 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user)); 3007 } 3008 } 3009 sPendingPackages.clear(); 3010 } 3011 } 3012 } 3013 3014 private class PackageUpdatedTask implements Runnable { 3015 int mOp; 3016 String[] mPackages; 3017 UserHandleCompat mUser; 3018 3019 public static final int OP_NONE = 0; 3020 public static final int OP_ADD = 1; 3021 public static final int OP_UPDATE = 2; 3022 public static final int OP_REMOVE = 3; // uninstlled 3023 public static final int OP_UNAVAILABLE = 4; // external media unmounted 3024 public static final int OP_SUSPEND = 5; // package suspended 3025 public static final int OP_UNSUSPEND = 6; // package unsuspended 3026 public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable 3027 3028 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) { 3029 mOp = op; 3030 mPackages = packages; 3031 mUser = user; 3032 } 3033 3034 public void run() { 3035 if (!mHasLoaderCompletedOnce) { 3036 // Loader has not yet run. 3037 return; 3038 } 3039 final Context context = mApp.getContext(); 3040 3041 final String[] packages = mPackages; 3042 final int N = packages.length; 3043 FlagOp flagOp = FlagOp.NO_OP; 3044 StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages))); 3045 switch (mOp) { 3046 case OP_ADD: { 3047 for (int i=0; i<N; i++) { 3048 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); 3049 mIconCache.updateIconsForPkg(packages[i], mUser); 3050 mBgAllAppsList.addPackage(context, packages[i], mUser); 3051 } 3052 3053 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); 3054 if (heuristic != null) { 3055 heuristic.processPackageAdd(mPackages); 3056 } 3057 break; 3058 } 3059 case OP_UPDATE: 3060 for (int i=0; i<N; i++) { 3061 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); 3062 mIconCache.updateIconsForPkg(packages[i], mUser); 3063 mBgAllAppsList.updatePackage(context, packages[i], mUser); 3064 mApp.getWidgetCache().removePackage(packages[i], mUser); 3065 } 3066 // Since package was just updated, the target must be available now. 3067 flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); 3068 break; 3069 case OP_REMOVE: { 3070 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); 3071 if (heuristic != null) { 3072 heuristic.processPackageRemoved(mPackages); 3073 } 3074 for (int i=0; i<N; i++) { 3075 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 3076 mIconCache.removeIconsForPkg(packages[i], mUser); 3077 } 3078 // Fall through 3079 } 3080 case OP_UNAVAILABLE: 3081 for (int i=0; i<N; i++) { 3082 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 3083 mBgAllAppsList.removePackage(packages[i], mUser); 3084 mApp.getWidgetCache().removePackage(packages[i], mUser); 3085 } 3086 flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); 3087 break; 3088 case OP_SUSPEND: 3089 case OP_UNSUSPEND: 3090 flagOp = mOp == OP_SUSPEND ? 3091 FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) : 3092 FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED); 3093 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N); 3094 mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp); 3095 break; 3096 case OP_USER_AVAILABILITY_CHANGE: 3097 flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser) 3098 ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER) 3099 : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER); 3100 // We want to update all packages for this user. 3101 pkgFilter = StringFilter.matchesAll(); 3102 mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp); 3103 break; 3104 } 3105 3106 ArrayList<AppInfo> added = null; 3107 ArrayList<AppInfo> modified = null; 3108 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>(); 3109 3110 if (mBgAllAppsList.added.size() > 0) { 3111 added = new ArrayList<>(mBgAllAppsList.added); 3112 mBgAllAppsList.added.clear(); 3113 } 3114 if (mBgAllAppsList.modified.size() > 0) { 3115 modified = new ArrayList<>(mBgAllAppsList.modified); 3116 mBgAllAppsList.modified.clear(); 3117 } 3118 if (mBgAllAppsList.removed.size() > 0) { 3119 removedApps.addAll(mBgAllAppsList.removed); 3120 mBgAllAppsList.removed.clear(); 3121 } 3122 3123 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>(); 3124 3125 if (added != null) { 3126 addAppsToAllApps(context, added); 3127 for (AppInfo ai : added) { 3128 addedOrUpdatedApps.put(ai.componentName, ai); 3129 } 3130 } 3131 3132 if (modified != null) { 3133 final Callbacks callbacks = getCallback(); 3134 final ArrayList<AppInfo> modifiedFinal = modified; 3135 for (AppInfo ai : modified) { 3136 addedOrUpdatedApps.put(ai.componentName, ai); 3137 } 3138 3139 mHandler.post(new Runnable() { 3140 public void run() { 3141 Callbacks cb = getCallback(); 3142 if (callbacks == cb && cb != null) { 3143 callbacks.bindAppsUpdated(modifiedFinal); 3144 } 3145 } 3146 }); 3147 } 3148 3149 // Update shortcut infos 3150 if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) { 3151 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>(); 3152 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>(); 3153 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>(); 3154 3155 synchronized (sBgLock) { 3156 for (ItemInfo info : sBgItemsIdMap) { 3157 if (info instanceof ShortcutInfo && mUser.equals(info.user)) { 3158 ShortcutInfo si = (ShortcutInfo) info; 3159 boolean infoUpdated = false; 3160 boolean shortcutUpdated = false; 3161 3162 // Update shortcuts which use iconResource. 3163 if ((si.iconResource != null) 3164 && pkgFilter.matches(si.iconResource.packageName)) { 3165 Bitmap icon = Utilities.createIconBitmap( 3166 si.iconResource.packageName, 3167 si.iconResource.resourceName, context); 3168 if (icon != null) { 3169 si.setIcon(icon); 3170 si.usingFallbackIcon = false; 3171 infoUpdated = true; 3172 } 3173 } 3174 3175 ComponentName cn = si.getTargetComponent(); 3176 if (cn != null && pkgFilter.matches(cn.getPackageName())) { 3177 AppInfo appInfo = addedOrUpdatedApps.get(cn); 3178 3179 if (si.isPromise()) { 3180 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { 3181 // Auto install icon 3182 PackageManager pm = context.getPackageManager(); 3183 ResolveInfo matched = pm.resolveActivity( 3184 new Intent(Intent.ACTION_MAIN) 3185 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER), 3186 PackageManager.MATCH_DEFAULT_ONLY); 3187 if (matched == null) { 3188 // Try to find the best match activity. 3189 Intent intent = pm.getLaunchIntentForPackage( 3190 cn.getPackageName()); 3191 if (intent != null) { 3192 cn = intent.getComponent(); 3193 appInfo = addedOrUpdatedApps.get(cn); 3194 } 3195 3196 if ((intent == null) || (appInfo == null)) { 3197 removedShortcuts.add(si); 3198 continue; 3199 } 3200 si.promisedIntent = intent; 3201 } 3202 } 3203 3204 // Restore the shortcut. 3205 if (appInfo != null) { 3206 si.flags = appInfo.flags; 3207 } 3208 3209 si.intent = si.promisedIntent; 3210 si.promisedIntent = null; 3211 si.status = ShortcutInfo.DEFAULT; 3212 infoUpdated = true; 3213 si.updateIcon(mIconCache); 3214 } 3215 3216 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction()) 3217 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 3218 si.updateIcon(mIconCache); 3219 si.title = Utilities.trim(appInfo.title); 3220 si.contentDescription = appInfo.contentDescription; 3221 infoUpdated = true; 3222 } 3223 3224 int oldDisabledFlags = si.isDisabled; 3225 si.isDisabled = flagOp.apply(si.isDisabled); 3226 if (si.isDisabled != oldDisabledFlags) { 3227 shortcutUpdated = true; 3228 } 3229 } 3230 3231 if (infoUpdated || shortcutUpdated) { 3232 updatedShortcuts.add(si); 3233 } 3234 if (infoUpdated) { 3235 updateItemInDatabase(context, si); 3236 } 3237 } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) { 3238 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; 3239 if (mUser.equals(widgetInfo.user) 3240 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) 3241 && pkgFilter.matches(widgetInfo.providerName.getPackageName())) { 3242 widgetInfo.restoreStatus &= 3243 ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY & 3244 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; 3245 3246 // adding this flag ensures that launcher shows 'click to setup' 3247 // if the widget has a config activity. In case there is no config 3248 // activity, it will be marked as 'restored' during bind. 3249 widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 3250 3251 widgets.add(widgetInfo); 3252 updateItemInDatabase(context, widgetInfo); 3253 } 3254 } 3255 } 3256 } 3257 3258 bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser); 3259 if (!removedShortcuts.isEmpty()) { 3260 deleteItemsFromDatabase(context, removedShortcuts); 3261 } 3262 3263 if (!widgets.isEmpty()) { 3264 final Callbacks callbacks = getCallback(); 3265 mHandler.post(new Runnable() { 3266 public void run() { 3267 Callbacks cb = getCallback(); 3268 if (callbacks == cb && cb != null) { 3269 callbacks.bindWidgetsRestored(widgets); 3270 } 3271 } 3272 }); 3273 } 3274 } 3275 3276 final HashSet<String> removedPackages = new HashSet<>(); 3277 final HashSet<ComponentName> removedComponents = new HashSet<>(); 3278 if (mOp == OP_REMOVE) { 3279 // Mark all packages in the broadcast to be removed 3280 Collections.addAll(removedPackages, packages); 3281 3282 // No need to update the removedComponents as 3283 // removedPackages is a super-set of removedComponents 3284 } else if (mOp == OP_UPDATE) { 3285 // Mark disabled packages in the broadcast to be removed 3286 for (int i=0; i<N; i++) { 3287 if (isPackageDisabled(context, packages[i], mUser)) { 3288 removedPackages.add(packages[i]); 3289 } 3290 } 3291 3292 // Update removedComponents as some components can get removed during package update 3293 for (AppInfo info : removedApps) { 3294 removedComponents.add(info.componentName); 3295 } 3296 } 3297 3298 if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { 3299 for (String pn : removedPackages) { 3300 deletePackageFromDatabase(context, pn, mUser); 3301 } 3302 for (ComponentName cn : removedComponents) { 3303 deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser)); 3304 } 3305 3306 // Remove any queued items from the install queue 3307 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); 3308 3309 // Call the components-removed callback 3310 final Callbacks callbacks = getCallback(); 3311 mHandler.post(new Runnable() { 3312 public void run() { 3313 Callbacks cb = getCallback(); 3314 if (callbacks == cb && cb != null) { 3315 callbacks.bindWorkspaceComponentsRemoved( 3316 removedPackages, removedComponents, mUser); 3317 } 3318 } 3319 }); 3320 } 3321 3322 if (!removedApps.isEmpty()) { 3323 // Remove corresponding apps from All-Apps 3324 final Callbacks callbacks = getCallback(); 3325 mHandler.post(new Runnable() { 3326 public void run() { 3327 Callbacks cb = getCallback(); 3328 if (callbacks == cb && cb != null) { 3329 callbacks.bindAppInfosRemoved(removedApps); 3330 } 3331 } 3332 }); 3333 } 3334 3335 // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to 3336 // get widget update signals. 3337 if (!Utilities.ATLEAST_MARSHMALLOW && 3338 (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) { 3339 final Callbacks callbacks = getCallback(); 3340 mHandler.post(new Runnable() { 3341 public void run() { 3342 Callbacks cb = getCallback(); 3343 if (callbacks == cb && cb != null) { 3344 callbacks.notifyWidgetProvidersChanged(); 3345 } 3346 } 3347 }); 3348 } 3349 } 3350 } 3351 3352 /** 3353 * Repopulates the shortcut info, possibly updating any icon already on the workspace. 3354 */ 3355 public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) { 3356 enqueueItemUpdatedTask(new Runnable() { 3357 @Override 3358 public void run() { 3359 info.updateFromDeepShortcutInfo( 3360 fullDetail, LauncherAppState.getInstance().getContext()); 3361 ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>(); 3362 update.add(info); 3363 bindUpdatedShortcuts(update, fullDetail.getUserHandle()); 3364 } 3365 }); 3366 } 3367 3368 private class ShortcutsChangedTask implements Runnable { 3369 private final String mPackageName; 3370 private final List<ShortcutInfoCompat> mShortcuts; 3371 private final UserHandleCompat mUser; 3372 private final boolean mUpdateIdMap; 3373 3374 public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts, 3375 UserHandleCompat user, boolean updateIdMap) { 3376 mPackageName = packageName; 3377 mShortcuts = shortcuts; 3378 mUser = user; 3379 mUpdateIdMap = updateIdMap; 3380 } 3381 3382 @Override 3383 public void run() { 3384 mDeepShortcutManager.onShortcutsChanged(mShortcuts); 3385 3386 // Find ShortcutInfo's that have changed on the workspace. 3387 final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>(); 3388 MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>(); 3389 for (ItemInfo itemInfo : sBgItemsIdMap) { 3390 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 3391 ShortcutInfo si = (ShortcutInfo) itemInfo; 3392 if (si.getPromisedIntent().getPackage().equals(mPackageName) 3393 && si.user.equals(mUser)) { 3394 idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si); 3395 } 3396 } 3397 } 3398 3399 final Context context = LauncherAppState.getInstance().getContext(); 3400 final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); 3401 if (!idsToWorkspaceShortcutInfos.isEmpty()) { 3402 // Update the workspace to reflect the changes to updated shortcuts residing on it. 3403 List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails( 3404 mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser); 3405 for (ShortcutInfoCompat fullDetails : shortcuts) { 3406 List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos 3407 .remove(fullDetails.getId()); 3408 if (!fullDetails.isPinned()) { 3409 // The shortcut was previously pinned but is no longer, so remove it from 3410 // the workspace and our pinned shortcut counts. 3411 // Note that we put this check here, after querying for full details, 3412 // because there's a possible race condition between pinning and 3413 // receiving this callback. 3414 removedShortcutInfos.addAll(shortcutInfos); 3415 continue; 3416 } 3417 for (ShortcutInfo shortcutInfo : shortcutInfos) { 3418 shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context); 3419 updatedShortcutInfos.add(shortcutInfo); 3420 } 3421 } 3422 } 3423 3424 // If there are still entries in idsToWorkspaceShortcutInfos, that means that 3425 // the corresponding shortcuts weren't passed in onShortcutsChanged(). This 3426 // means they were cleared, so we remove and unpin them now. 3427 for (String id : idsToWorkspaceShortcutInfos.keySet()) { 3428 removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id)); 3429 } 3430 3431 bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser); 3432 if (!removedShortcutInfos.isEmpty()) { 3433 deleteItemsFromDatabase(context, removedShortcutInfos); 3434 } 3435 3436 if (mUpdateIdMap) { 3437 // Update the deep shortcut map if the list of ids has changed for an activity. 3438 updateDeepShortcutMap(mPackageName, mUser, mShortcuts); 3439 bindDeepShortcuts(); 3440 } 3441 } 3442 } 3443 3444 /** 3445 * Task to handle changing of lock state of the user 3446 */ 3447 private class UserLockStateChangedTask implements Runnable { 3448 3449 private final UserHandleCompat mUser; 3450 3451 public UserLockStateChangedTask(UserHandleCompat user) { 3452 mUser = user; 3453 } 3454 3455 @Override 3456 public void run() { 3457 boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser); 3458 Context context = mApp.getContext(); 3459 3460 HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>(); 3461 if (isUserUnlocked) { 3462 List<ShortcutInfoCompat> shortcuts = 3463 mDeepShortcutManager.queryForPinnedShortcuts(null, mUser); 3464 if (mDeepShortcutManager.wasLastCallSuccess()) { 3465 for (ShortcutInfoCompat shortcut : shortcuts) { 3466 pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); 3467 } 3468 } else { 3469 // Shortcut manager can fail due to some race condition when the lock state 3470 // changes too frequently. For the purpose of the update, 3471 // consider it as still locked. 3472 isUserUnlocked = false; 3473 } 3474 } 3475 3476 // Update the workspace to reflect the changes to updated shortcuts residing on it. 3477 ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); 3478 ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>(); 3479 for (ItemInfo itemInfo : sBgItemsIdMap) { 3480 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT 3481 && mUser.equals(itemInfo.user)) { 3482 ShortcutInfo si = (ShortcutInfo) itemInfo; 3483 if (isUserUnlocked) { 3484 ShortcutInfoCompat shortcut = 3485 pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si)); 3486 // We couldn't verify the shortcut during loader. If its no longer available 3487 // (probably due to clear data), delete the workspace item as well 3488 if (shortcut == null) { 3489 deletedShortcutInfos.add(si); 3490 continue; 3491 } 3492 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER; 3493 si.updateFromDeepShortcutInfo(shortcut, context); 3494 } else { 3495 si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; 3496 } 3497 updatedShortcutInfos.add(si); 3498 } 3499 } 3500 bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser); 3501 if (!deletedShortcutInfos.isEmpty()) { 3502 deleteItemsFromDatabase(context, deletedShortcutInfos); 3503 } 3504 3505 // Remove shortcut id map for that user 3506 Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator(); 3507 while (keysIter.hasNext()) { 3508 if (keysIter.next().user.equals(mUser)) { 3509 keysIter.remove(); 3510 } 3511 } 3512 3513 if (isUserUnlocked) { 3514 updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser)); 3515 } 3516 bindDeepShortcuts(); 3517 } 3518 } 3519 3520 private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) { 3521 mHandler.post(new Runnable() { 3522 @Override 3523 public void run() { 3524 Callbacks cb = getCallback(); 3525 if (callbacks == cb && cb != null) { 3526 callbacks.bindWidgetsModel(model); 3527 } 3528 } 3529 }); 3530 } 3531 3532 public void refreshAndBindWidgetsAndShortcuts( 3533 final Callbacks callbacks, final boolean bindFirst) { 3534 runOnWorkerThread(new Runnable() { 3535 @Override 3536 public void run() { 3537 if (bindFirst && !mBgWidgetsModel.isEmpty()) { 3538 bindWidgetsModel(callbacks, mBgWidgetsModel.clone()); 3539 } 3540 final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext()); 3541 bindWidgetsModel(callbacks, model); 3542 // update the Widget entries inside DB on the worker thread. 3543 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews( 3544 model.getRawList()); 3545 } 3546 }); 3547 } 3548 3549 @Thunk static boolean isPackageDisabled(Context context, String packageName, 3550 UserHandleCompat user) { 3551 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 3552 return !launcherApps.isPackageEnabledForProfile(packageName, user); 3553 } 3554 3555 public static boolean isValidPackageActivity(Context context, ComponentName cn, 3556 UserHandleCompat user) { 3557 if (cn == null) { 3558 return false; 3559 } 3560 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 3561 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) { 3562 return false; 3563 } 3564 return launcherApps.isActivityEnabledForProfile(cn, user); 3565 } 3566 3567 public static boolean isValidPackage(Context context, String packageName, 3568 UserHandleCompat user) { 3569 if (packageName == null) { 3570 return false; 3571 } 3572 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 3573 return launcherApps.isPackageEnabledForProfile(packageName, user); 3574 } 3575 3576 /** 3577 * Make an ShortcutInfo object for a restored application or shortcut item that points 3578 * to a package that is not yet installed on the system. 3579 */ 3580 public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent, 3581 int promiseType, int itemType, CursorIconInfo iconInfo) { 3582 final ShortcutInfo info = new ShortcutInfo(); 3583 info.user = UserHandleCompat.myUserHandle(); 3584 3585 Bitmap icon = iconInfo.loadIcon(c, info); 3586 // the fallback icon 3587 if (icon == null) { 3588 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */); 3589 } else { 3590 info.setIcon(icon); 3591 } 3592 3593 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) { 3594 String title = iconInfo.getTitle(c); 3595 if (!TextUtils.isEmpty(title)) { 3596 info.title = Utilities.trim(title); 3597 } 3598 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { 3599 if (TextUtils.isEmpty(info.title)) { 3600 info.title = iconInfo.getTitle(c); 3601 } 3602 } else { 3603 throw new InvalidParameterException("Invalid restoreType " + promiseType); 3604 } 3605 3606 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); 3607 info.itemType = itemType; 3608 info.promisedIntent = intent; 3609 info.status = promiseType; 3610 return info; 3611 } 3612 3613 /** 3614 * Make an Intent object for a restored application or shortcut item that points 3615 * to the market page for the item. 3616 */ 3617 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) { 3618 ComponentName componentName = intent.getComponent(); 3619 return getMarketIntent(componentName.getPackageName()); 3620 } 3621 3622 static Intent getMarketIntent(String packageName) { 3623 return new Intent(Intent.ACTION_VIEW) 3624 .setData(new Uri.Builder() 3625 .scheme("market") 3626 .authority("details") 3627 .appendQueryParameter("id", packageName) 3628 .build()); 3629 } 3630 3631 /** 3632 * Make an ShortcutInfo object for a shortcut that is an application. 3633 * 3634 * If c is not null, then it will be used to fill in missing data like the title and icon. 3635 */ 3636 public ShortcutInfo getAppShortcutInfo(Intent intent, 3637 UserHandleCompat user, Cursor c, CursorIconInfo iconInfo, 3638 boolean allowMissingTarget, boolean useLowResIcon) { 3639 if (user == null) { 3640 Log.d(TAG, "Null user found in getShortcutInfo"); 3641 return null; 3642 } 3643 3644 ComponentName componentName = intent.getComponent(); 3645 if (componentName == null) { 3646 Log.d(TAG, "Missing component found in getShortcutInfo"); 3647 return null; 3648 } 3649 3650 Intent newIntent = new Intent(intent.getAction(), null); 3651 newIntent.addCategory(Intent.CATEGORY_LAUNCHER); 3652 newIntent.setComponent(componentName); 3653 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user); 3654 if ((lai == null) && !allowMissingTarget) { 3655 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName); 3656 return null; 3657 } 3658 3659 final ShortcutInfo info = new ShortcutInfo(); 3660 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon); 3661 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) { 3662 Bitmap icon = iconInfo.loadIcon(c); 3663 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon); 3664 } 3665 3666 if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) { 3667 info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED; 3668 } 3669 3670 // from the db 3671 if (TextUtils.isEmpty(info.title) && c != null) { 3672 info.title = iconInfo.getTitle(c); 3673 } 3674 3675 // fall back to the class name of the activity 3676 if (info.title == null) { 3677 info.title = componentName.getClassName(); 3678 } 3679 3680 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 3681 info.user = user; 3682 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); 3683 if (lai != null) { 3684 info.flags = AppInfo.initFlags(lai); 3685 } 3686 return info; 3687 } 3688 3689 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, 3690 ItemInfoFilter f) { 3691 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>(); 3692 for (ItemInfo i : infos) { 3693 if (i instanceof ShortcutInfo) { 3694 ShortcutInfo info = (ShortcutInfo) i; 3695 ComponentName cn = info.getTargetComponent(); 3696 if (cn != null && f.filterItem(null, info, cn)) { 3697 filtered.add(info); 3698 } 3699 } else if (i instanceof FolderInfo) { 3700 FolderInfo info = (FolderInfo) i; 3701 for (ShortcutInfo s : info.contents) { 3702 ComponentName cn = s.getTargetComponent(); 3703 if (cn != null && f.filterItem(info, s, cn)) { 3704 filtered.add(s); 3705 } 3706 } 3707 } else if (i instanceof LauncherAppWidgetInfo) { 3708 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i; 3709 ComponentName cn = info.providerName; 3710 if (cn != null && f.filterItem(null, info, cn)) { 3711 filtered.add(info); 3712 } 3713 } 3714 } 3715 return new ArrayList<ItemInfo>(filtered); 3716 } 3717 3718 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname, 3719 final UserHandleCompat user) { 3720 ItemInfoFilter filter = new ItemInfoFilter() { 3721 @Override 3722 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { 3723 if (info.user == null) { 3724 return cn.equals(cname); 3725 } else { 3726 return cn.equals(cname) && info.user.equals(user); 3727 } 3728 } 3729 }; 3730 return filterItemInfos(sBgItemsIdMap, filter); 3731 } 3732 3733 /** 3734 * Make an ShortcutInfo object for a shortcut that isn't an application. 3735 */ 3736 @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) { 3737 final ShortcutInfo info = new ShortcutInfo(); 3738 // Non-app shortcuts are only supported for current user. 3739 info.user = UserHandleCompat.myUserHandle(); 3740 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 3741 3742 // TODO: If there's an explicit component and we can't install that, delete it. 3743 3744 loadInfoFromCursor(info, c, iconInfo); 3745 return info; 3746 } 3747 3748 /** 3749 * Make an ShortcutInfo object for a shortcut that isn't an application. 3750 */ 3751 public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) { 3752 info.title = iconInfo.getTitle(c); 3753 Bitmap icon = iconInfo.loadIcon(c, info); 3754 // the fallback icon 3755 if (icon == null) { 3756 icon = mIconCache.getDefaultIcon(info.user); 3757 info.usingFallbackIcon = true; 3758 } 3759 info.setIcon(icon); 3760 } 3761 3762 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) { 3763 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 3764 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 3765 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 3766 3767 if (intent == null) { 3768 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null 3769 Log.e(TAG, "Can't construct ShorcutInfo with null intent"); 3770 return null; 3771 } 3772 3773 Bitmap icon = null; 3774 boolean customIcon = false; 3775 ShortcutIconResource iconResource = null; 3776 3777 if (bitmap instanceof Bitmap) { 3778 icon = Utilities.createIconBitmap((Bitmap) bitmap, context); 3779 customIcon = true; 3780 } else { 3781 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 3782 if (extra instanceof ShortcutIconResource) { 3783 iconResource = (ShortcutIconResource) extra; 3784 icon = Utilities.createIconBitmap(iconResource.packageName, 3785 iconResource.resourceName, context); 3786 } 3787 } 3788 3789 final ShortcutInfo info = new ShortcutInfo(); 3790 3791 // Only support intents for current user for now. Intents sent from other 3792 // users wouldn't get here without intent forwarding anyway. 3793 info.user = UserHandleCompat.myUserHandle(); 3794 if (icon == null) { 3795 icon = mIconCache.getDefaultIcon(info.user); 3796 info.usingFallbackIcon = true; 3797 } 3798 info.setIcon(icon); 3799 3800 info.title = Utilities.trim(name); 3801 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); 3802 info.intent = intent; 3803 info.iconResource = iconResource; 3804 3805 return info; 3806 } 3807 3808 /** 3809 * Return an existing FolderInfo object if we have encountered this ID previously, 3810 * or make a new one. 3811 */ 3812 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) { 3813 // See if a placeholder was created for us already 3814 FolderInfo folderInfo = folders.get(id); 3815 if (folderInfo == null) { 3816 // No placeholder -- create a new instance 3817 folderInfo = new FolderInfo(); 3818 folders.put(id, folderInfo); 3819 } 3820 return folderInfo; 3821 } 3822 3823 3824 static boolean isValidProvider(AppWidgetProviderInfo provider) { 3825 return (provider != null) && (provider.provider != null) 3826 && (provider.provider.getPackageName() != null); 3827 } 3828 3829 public void dumpState() { 3830 Log.d(TAG, "mCallbacks=" + mCallbacks); 3831 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data); 3832 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added); 3833 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed); 3834 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified); 3835 if (mLoaderTask != null) { 3836 mLoaderTask.dumpState(); 3837 } else { 3838 Log.d(TAG, "mLoaderTask=null"); 3839 } 3840 } 3841 3842 public Callbacks getCallback() { 3843 return mCallbacks != null ? mCallbacks.get() : null; 3844 } 3845 3846 /** 3847 * @return {@link FolderInfo} if its already loaded. 3848 */ 3849 public FolderInfo findFolderById(Long folderId) { 3850 synchronized (sBgLock) { 3851 return sBgFolders.get(folderId); 3852 } 3853 } 3854 3855 @Thunk class DeferredMainThreadExecutor implements Executor { 3856 3857 @Override 3858 public void execute(Runnable command) { 3859 runOnMainThread(command); 3860 } 3861 } 3862 3863 /** 3864 * @return the looper for the worker thread which can be used to start background tasks. 3865 */ 3866 public static Looper getWorkerLooper() { 3867 return sWorkerThread.getLooper(); 3868 } 3869} 3870