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