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