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