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