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