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