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