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