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