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