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