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