2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3;
19 import android.appwidget.AppWidgetProviderInfo;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.ContentProviderOperation;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.Intent.ShortcutIconResource;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.net.Uri;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.Looper;
37 import android.os.Parcelable;
38 import android.os.Process;
39 import android.os.SystemClock;
40 import android.os.Trace;
41 import android.provider.BaseColumns;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.LongSparseArray;
45 import android.util.MutableInt;
46 import android.util.Pair;
48 import com.android.launcher3.compat.AppWidgetManagerCompat;
49 import com.android.launcher3.compat.LauncherActivityInfoCompat;
50 import com.android.launcher3.compat.LauncherAppsCompat;
51 import com.android.launcher3.compat.PackageInstallerCompat;
52 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
53 import com.android.launcher3.compat.UserHandleCompat;
54 import com.android.launcher3.compat.UserManagerCompat;
55 import com.android.launcher3.config.FeatureFlags;
56 import com.android.launcher3.config.ProviderConfig;
57 import com.android.launcher3.dynamicui.ExtractionUtils;
58 import com.android.launcher3.folder.Folder;
59 import com.android.launcher3.folder.FolderIcon;
60 import com.android.launcher3.logging.FileLog;
61 import com.android.launcher3.model.GridSizeMigrationTask;
62 import com.android.launcher3.model.WidgetsModel;
63 import com.android.launcher3.provider.ImportDataTask;
64 import com.android.launcher3.provider.LauncherDbUtils;
65 import com.android.launcher3.shortcuts.DeepShortcutManager;
66 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
67 import com.android.launcher3.shortcuts.ShortcutKey;
68 import com.android.launcher3.util.ComponentKey;
69 import com.android.launcher3.util.CursorIconInfo;
70 import com.android.launcher3.util.FlagOp;
71 import com.android.launcher3.util.GridOccupancy;
72 import com.android.launcher3.util.LongArrayMap;
73 import com.android.launcher3.util.ManagedProfileHeuristic;
74 import com.android.launcher3.util.MultiHashMap;
75 import com.android.launcher3.util.PackageManagerHelper;
76 import com.android.launcher3.util.Preconditions;
77 import com.android.launcher3.util.StringFilter;
78 import com.android.launcher3.util.Thunk;
79 import com.android.launcher3.util.ViewOnDrawExecutor;
81 import java.lang.ref.WeakReference;
82 import java.net.URISyntaxException;
83 import java.security.InvalidParameterException;
84 import java.util.ArrayList;
85 import java.util.Arrays;
86 import java.util.Collections;
87 import java.util.Comparator;
88 import java.util.HashMap;
89 import java.util.HashSet;
90 import java.util.Iterator;
91 import java.util.List;
93 import java.util.Map.Entry;
95 import java.util.concurrent.Executor;
98 * Maintains in-memory state of the Launcher. It is expected that there should be only one
99 * LauncherModel object held in a static. Also provide APIs for updating the database state
102 public class LauncherModel extends BroadcastReceiver
103 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
104 static final boolean DEBUG_LOADERS = false;
105 private static final boolean DEBUG_RECEIVER = false;
107 static final String TAG = "Launcher.Model";
109 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
110 private static final long INVALID_SCREEN_ID = -1L;
112 @Thunk final LauncherAppState mApp;
113 @Thunk final Object mLock = new Object();
114 @Thunk DeferredHandler mHandler = new DeferredHandler();
115 @Thunk LoaderTask mLoaderTask;
116 @Thunk boolean mIsLoaderTaskRunning;
117 @Thunk boolean mHasLoaderCompletedOnce;
119 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
121 sWorkerThread.start();
123 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
125 // We start off with everything not loaded. After that, we assume that
126 // our monitoring of the package manager provides all updates and we never
127 // need to do a requery. These are only ever touched from the loader thread.
128 private boolean mWorkspaceLoaded;
129 private boolean mAllAppsLoaded;
130 private boolean mDeepShortcutsLoaded;
133 * Set of runnables to be called on the background thread after the workspace binding
136 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
138 @Thunk WeakReference<Callbacks> mCallbacks;
140 // < only access in worker thread >
141 private final AllAppsList mBgAllAppsList;
142 // Entire list of widgets.
143 private final WidgetsModel mBgWidgetsModel;
145 // Maps all launcher activities to the id's of their shortcuts (if they have any).
146 private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
148 private boolean mHasShortcutHostPermission;
149 // Runnable to check if the shortcuts permission has changed.
150 private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
153 if (mDeepShortcutsLoaded) {
154 boolean hasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
155 if (hasShortcutHostPermission != mHasShortcutHostPermission) {
156 mApp.reloadWorkspace();
162 // The lock that must be acquired before referencing any static bg data structures. Unlike
163 // other locks, this one can generally be held long-term because we never expect any of these
164 // static data structures to be referenced outside of the worker thread except on the first
165 // load after configuration change.
166 static final Object sBgLock = new Object();
168 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
169 // LauncherModel to their ids
170 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
172 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
173 // created by LauncherModel that are directly on the home screen (however, no widgets or
174 // shortcuts within folders).
175 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
177 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
178 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
179 new ArrayList<LauncherAppWidgetInfo>();
181 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
182 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
184 // sBgWorkspaceScreens is the ordered set of workspace screens.
185 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
187 // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
188 // times it is pinned.
189 static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
191 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
192 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
193 new HashMap<UserHandleCompat, HashSet<String>>();
195 // </ only access in worker thread >
197 private IconCache mIconCache;
198 private DeepShortcutManager mDeepShortcutManager;
200 private final LauncherAppsCompat mLauncherApps;
201 private final UserManagerCompat mUserManager;
203 public interface Callbacks {
204 public boolean setLoadOnResume();
205 public int getCurrentWorkspaceScreen();
206 public void clearPendingBinds();
207 public void startBinding();
208 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
209 boolean forceAnimateIcons);
210 public void bindScreens(ArrayList<Long> orderedScreenIds);
211 public void finishFirstPageBind(ViewOnDrawExecutor executor);
212 public void finishBindingItems();
213 public void bindAppWidget(LauncherAppWidgetInfo info);
214 public void bindAllApplications(ArrayList<AppInfo> apps);
215 public void bindAppsAdded(ArrayList<Long> newScreens,
216 ArrayList<ItemInfo> addNotAnimated,
217 ArrayList<ItemInfo> addAnimated,
218 ArrayList<AppInfo> addedApps);
219 public void bindAppsUpdated(ArrayList<AppInfo> apps);
220 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
221 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
222 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
223 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
224 public void bindWorkspaceComponentsRemoved(
225 HashSet<String> packageNames, HashSet<ComponentName> components,
226 UserHandleCompat user);
227 public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
228 public void notifyWidgetProvidersChanged();
229 public void bindWidgetsModel(WidgetsModel model);
230 public void onPageBoundSynchronously(int page);
231 public void executeOnNextDraw(ViewOnDrawExecutor executor);
232 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
235 public interface ItemInfoFilter {
236 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
239 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
240 DeepShortcutManager deepShortcutManager) {
241 Context context = app.getContext();
243 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
244 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
245 mIconCache = iconCache;
246 mDeepShortcutManager = deepShortcutManager;
248 mLauncherApps = LauncherAppsCompat.getInstance(context);
249 mUserManager = UserManagerCompat.getInstance(context);
252 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
253 * posted on the main thread handler. */
254 private void runOnMainThread(Runnable r) {
255 if (sWorkerThread.getThreadId() == Process.myTid()) {
256 // If we are on the worker thread, post onto the main handler
263 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
264 * posted on the worker thread handler. */
265 private static void runOnWorkerThread(Runnable r) {
266 if (sWorkerThread.getThreadId() == Process.myTid()) {
269 // If we are not on the worker thread, then post to the worker handler
274 public void setPackageState(final PackageInstallInfo installInfo) {
275 Runnable updateRunnable = new Runnable() {
279 synchronized (sBgLock) {
280 final HashSet<ItemInfo> updates = new HashSet<>();
282 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
283 // Ignore install success events as they are handled by Package add events.
287 for (ItemInfo info : sBgItemsIdMap) {
288 if (info instanceof ShortcutInfo) {
289 ShortcutInfo si = (ShortcutInfo) info;
290 ComponentName cn = si.getTargetComponent();
291 if (si.isPromise() && (cn != null)
292 && installInfo.packageName.equals(cn.getPackageName())) {
293 si.setInstallProgress(installInfo.progress);
295 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
296 // Mark this info as broken.
297 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
304 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
305 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
306 widget.installProgress = installInfo.progress;
311 if (!updates.isEmpty()) {
312 // Push changes to the callback.
313 Runnable r = new Runnable() {
315 Callbacks callbacks = getCallback();
316 if (callbacks != null) {
317 callbacks.bindRestoreItemsChange(updates);
326 runOnWorkerThread(updateRunnable);
330 * Updates the icons and label of all pending icons for the provided package name.
332 public void updateSessionDisplayInfo(final String packageName) {
333 Runnable updateRunnable = new Runnable() {
337 synchronized (sBgLock) {
338 ArrayList<ShortcutInfo> updates = new ArrayList<>();
339 UserHandleCompat user = UserHandleCompat.myUserHandle();
341 for (ItemInfo info : sBgItemsIdMap) {
342 if (info instanceof ShortcutInfo) {
343 ShortcutInfo si = (ShortcutInfo) info;
344 ComponentName cn = si.getTargetComponent();
345 if (si.isPromise() && (cn != null)
346 && packageName.equals(cn.getPackageName())) {
347 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
348 // For auto install apps update the icon as well as label.
349 mIconCache.getTitleAndIcon(si,
350 si.promisedIntent, user,
351 si.shouldUseLowResIcon());
353 // Only update the icon for restored apps.
354 si.updateIcon(mIconCache);
361 bindUpdatedShortcuts(updates, user);
365 runOnWorkerThread(updateRunnable);
368 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
369 final Callbacks callbacks = getCallback();
371 if (allAppsApps == null) {
372 throw new RuntimeException("allAppsApps must not be null");
374 if (allAppsApps.isEmpty()) {
378 // Process the newly added applications and add them to the database first
379 Runnable r = new Runnable() {
381 runOnMainThread(new Runnable() {
383 Callbacks cb = getCallback();
384 if (callbacks == cb && cb != null) {
385 callbacks.bindAppsAdded(null, null, null, allAppsApps);
391 runOnWorkerThread(r);
394 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
395 int[] xy, int spanX, int spanY) {
396 LauncherAppState app = LauncherAppState.getInstance();
397 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
399 GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
400 if (occupiedPos != null) {
401 for (ItemInfo r : occupiedPos) {
402 occupied.markCells(r, true);
405 return occupied.findVacantCell(xy, spanX, spanY);
409 * Find a position on the screen for the given size or adds a new screen.
410 * @return screenId and the coordinates for the item.
412 @Thunk Pair<Long, int[]> findSpaceForItem(
414 ArrayList<Long> workspaceScreens,
415 ArrayList<Long> addedWorkspaceScreensFinal,
416 int spanX, int spanY) {
417 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
419 // Use sBgItemsIdMap as all the items are already loaded.
420 assertWorkspaceLoaded();
421 synchronized (sBgLock) {
422 for (ItemInfo info : sBgItemsIdMap) {
423 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
424 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
426 items = new ArrayList<>();
427 screenItems.put(info.screenId, items);
434 // Find appropriate space for the item.
436 int[] cordinates = new int[2];
437 boolean found = false;
439 int screenCount = workspaceScreens.size();
440 // First check the preferred screen.
441 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
442 if (preferredScreenIndex < screenCount) {
443 screenId = workspaceScreens.get(preferredScreenIndex);
444 found = findNextAvailableIconSpaceInScreen(
445 screenItems.get(screenId), cordinates, spanX, spanY);
449 // Search on any of the screens starting from the first screen.
450 for (int screen = 1; screen < screenCount; screen++) {
451 screenId = workspaceScreens.get(screen);
452 if (findNextAvailableIconSpaceInScreen(
453 screenItems.get(screenId), cordinates, spanX, spanY)) {
454 // We found a space for it
462 // Still no position found. Add a new screen to the end.
463 screenId = LauncherSettings.Settings.call(context.getContentResolver(),
464 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
465 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
467 // Save the screen id for binding in the workspace
468 workspaceScreens.add(screenId);
469 addedWorkspaceScreensFinal.add(screenId);
471 // If we still can't find an empty space, then God help us all!!!
472 if (!findNextAvailableIconSpaceInScreen(
473 screenItems.get(screenId), cordinates, spanX, spanY)) {
474 throw new RuntimeException("Can't find space to add the item");
477 return Pair.create(screenId, cordinates);
481 * Adds the provided items to the workspace.
483 public void addAndBindAddedWorkspaceItems(final Context context,
484 final ArrayList<? extends ItemInfo> workspaceApps) {
485 final Callbacks callbacks = getCallback();
486 if (workspaceApps.isEmpty()) {
489 // Process the newly added applications and add them to the database first
490 Runnable r = new Runnable() {
492 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
493 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
495 // Get the list of workspace screens. We need to append to this list and
496 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
498 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
499 synchronized(sBgLock) {
500 for (ItemInfo item : workspaceApps) {
501 if (item instanceof ShortcutInfo) {
502 // Short-circuit this logic if the icon exists somewhere on the workspace
503 if (shortcutExists(context, item.getIntent(), item.user)) {
508 // Find appropriate space for the item.
509 Pair<Long, int[]> coords = findSpaceForItem(context,
510 workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
511 long screenId = coords.first;
512 int[] cordinates = coords.second;
515 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
517 } else if (item instanceof AppInfo) {
518 itemInfo = ((AppInfo) item).makeShortcut();
520 throw new RuntimeException("Unexpected info type");
523 // Add the shortcut to the db
524 addItemToDatabase(context, itemInfo,
525 LauncherSettings.Favorites.CONTAINER_DESKTOP,
526 screenId, cordinates[0], cordinates[1]);
527 // Save the ShortcutInfo for binding in the workspace
528 addedShortcutsFinal.add(itemInfo);
532 // Update the workspace screens
533 updateWorkspaceScreenOrder(context, workspaceScreens);
535 if (!addedShortcutsFinal.isEmpty()) {
536 runOnMainThread(new Runnable() {
538 Callbacks cb = getCallback();
539 if (callbacks == cb && cb != null) {
540 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
541 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
542 if (!addedShortcutsFinal.isEmpty()) {
543 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
544 long lastScreenId = info.screenId;
545 for (ItemInfo i : addedShortcutsFinal) {
546 if (i.screenId == lastScreenId) {
549 addNotAnimated.add(i);
553 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
554 addNotAnimated, addAnimated, null);
561 runOnWorkerThread(r);
565 * Adds an item to the DB if it was not created previously, or move it to a new
566 * <container, screen, cellX, cellY>
568 public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
569 long screenId, int cellX, int cellY) {
570 if (item.container == ItemInfo.NO_ID) {
572 addItemToDatabase(context, item, container, screenId, cellX, cellY);
574 // From somewhere else
575 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
579 static void checkItemInfoLocked(
580 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
581 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
582 if (modelItem != null && item != modelItem) {
583 // check all the data is consistent
584 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
585 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
586 ShortcutInfo shortcut = (ShortcutInfo) item;
587 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
588 modelShortcut.intent.filterEquals(shortcut.intent) &&
589 modelShortcut.id == shortcut.id &&
590 modelShortcut.itemType == shortcut.itemType &&
591 modelShortcut.container == shortcut.container &&
592 modelShortcut.screenId == shortcut.screenId &&
593 modelShortcut.cellX == shortcut.cellX &&
594 modelShortcut.cellY == shortcut.cellY &&
595 modelShortcut.spanX == shortcut.spanX &&
596 modelShortcut.spanY == shortcut.spanY) {
597 // For all intents and purposes, this is the same object
602 // the modelItem needs to match up perfectly with item if our model is
603 // to be consistent with the database-- for now, just require
604 // modelItem == item or the equality check above
605 String msg = "item: " + ((item != null) ? item.toString() : "null") +
607 ((modelItem != null) ? modelItem.toString() : "null") +
608 "Error: ItemInfo passed to checkItemInfo doesn't match original";
609 RuntimeException e = new RuntimeException(msg);
610 if (stackTrace != null) {
611 e.setStackTrace(stackTrace);
617 static void checkItemInfo(final ItemInfo item) {
618 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
619 final long itemId = item.id;
620 Runnable r = new Runnable() {
622 synchronized (sBgLock) {
623 checkItemInfoLocked(itemId, item, stackTrace);
627 runOnWorkerThread(r);
630 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
631 final ItemInfo item, final String callingFunction) {
632 final long itemId = item.id;
633 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
634 final ContentResolver cr = context.getContentResolver();
636 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
637 Runnable r = new Runnable() {
639 cr.update(uri, values, null, null);
640 updateItemArrays(item, itemId, stackTrace);
643 runOnWorkerThread(r);
646 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
647 final ArrayList<ItemInfo> items, final String callingFunction) {
648 final ContentResolver cr = context.getContentResolver();
650 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
651 Runnable r = new Runnable() {
653 ArrayList<ContentProviderOperation> ops =
654 new ArrayList<ContentProviderOperation>();
655 int count = items.size();
656 for (int i = 0; i < count; i++) {
657 ItemInfo item = items.get(i);
658 final long itemId = item.id;
659 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
660 ContentValues values = valuesList.get(i);
662 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
663 updateItemArrays(item, itemId, stackTrace);
667 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
668 } catch (Exception e) {
673 runOnWorkerThread(r);
676 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
677 // Lock on mBgLock *after* the db operation
678 synchronized (sBgLock) {
679 checkItemInfoLocked(itemId, item, stackTrace);
681 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
682 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
683 // Item is in a folder, make sure this folder exists
684 if (!sBgFolders.containsKey(item.container)) {
685 // An items container is being set to a that of an item which is not in
686 // the list of Folders.
687 String msg = "item: " + item + " container being set to: " +
688 item.container + ", not in the list of folders";
693 // Items are added/removed from the corresponding FolderInfo elsewhere, such
694 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
695 // that are on the desktop, as appropriate
696 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
697 if (modelItem != null &&
698 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
699 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
700 switch (modelItem.itemType) {
701 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
702 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
703 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
704 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
705 if (!sBgWorkspaceItems.contains(modelItem)) {
706 sBgWorkspaceItems.add(modelItem);
713 sBgWorkspaceItems.remove(modelItem);
719 * Move an item in the DB to a new <container, screen, cellX, cellY>
721 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
722 final long screenId, final int cellX, final int cellY) {
723 item.container = container;
727 // We store hotseat items in canonical form which is this orientation invariant position
729 if (context instanceof Launcher && screenId < 0 &&
730 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
731 item.screenId = Launcher.getLauncher(context).getHotseat()
732 .getOrderInHotseat(cellX, cellY);
734 item.screenId = screenId;
737 final ContentValues values = new ContentValues();
738 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
739 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
740 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
741 values.put(LauncherSettings.Favorites.RANK, item.rank);
742 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
744 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
748 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
749 * cellX, cellY have already been updated on the ItemInfos.
751 public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
752 final long container, final int screen) {
754 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
755 int count = items.size();
757 for (int i = 0; i < count; i++) {
758 ItemInfo item = items.get(i);
759 item.container = container;
761 // We store hotseat items in canonical form which is this orientation invariant position
763 if (context instanceof Launcher && screen < 0 &&
764 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
765 item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX,
768 item.screenId = screen;
771 final ContentValues values = new ContentValues();
772 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
773 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
774 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
775 values.put(LauncherSettings.Favorites.RANK, item.rank);
776 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
778 contentValues.add(values);
780 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
784 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
786 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
787 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
788 item.container = container;
794 // We store hotseat items in canonical form which is this orientation invariant position
796 if (context instanceof Launcher && screenId < 0 &&
797 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
798 item.screenId = Launcher.getLauncher(context).getHotseat()
799 .getOrderInHotseat(cellX, cellY);
801 item.screenId = screenId;
804 final ContentValues values = new ContentValues();
805 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
806 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
807 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
808 values.put(LauncherSettings.Favorites.RANK, item.rank);
809 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
810 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
811 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
813 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
817 * Update an item to the database in a specified container.
819 public static void updateItemInDatabase(Context context, final ItemInfo item) {
820 final ContentValues values = new ContentValues();
821 item.onAddToDatabase(context, values);
822 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
825 private void assertWorkspaceLoaded() {
826 if (ProviderConfig.IS_DOGFOOD_BUILD) {
827 synchronized (mLock) {
828 if (!mHasLoaderCompletedOnce ||
829 (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
830 throw new RuntimeException("Trying to add shortcut while loader is running");
837 * Returns true if the shortcuts already exists on the workspace. This must be called after
838 * the workspace has been loaded. We identify a shortcut by its intent.
840 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
841 assertWorkspaceLoaded();
842 final String intentWithPkg, intentWithoutPkg;
843 if (intent.getComponent() != null) {
844 // If component is not null, an intent with null package will produce
845 // the same result and should also be a match.
846 String packageName = intent.getComponent().getPackageName();
847 if (intent.getPackage() != null) {
848 intentWithPkg = intent.toUri(0);
849 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
851 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
852 intentWithoutPkg = intent.toUri(0);
855 intentWithPkg = intent.toUri(0);
856 intentWithoutPkg = intent.toUri(0);
859 synchronized (sBgLock) {
860 for (ItemInfo item : sBgItemsIdMap) {
861 if (item instanceof ShortcutInfo) {
862 ShortcutInfo info = (ShortcutInfo) item;
863 Intent targetIntent = info.promisedIntent == null
864 ? info.intent : info.promisedIntent;
865 if (targetIntent != null && info.user.equals(user)) {
866 Intent copyIntent = new Intent(targetIntent);
867 copyIntent.setSourceBounds(intent.getSourceBounds());
868 String s = copyIntent.toUri(0);
869 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
880 * Add an item to the database in a specified container. Sets the container, screen, cellX and
881 * cellY fields of the item. Also assigns an ID to the item.
883 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
884 final long screenId, final int cellX, final int cellY) {
885 item.container = container;
888 // We store hotseat items in canonical form which is this orientation invariant position
890 if (context instanceof Launcher && screenId < 0 &&
891 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
892 item.screenId = Launcher.getLauncher(context).getHotseat()
893 .getOrderInHotseat(cellX, cellY);
895 item.screenId = screenId;
898 final ContentValues values = new ContentValues();
899 final ContentResolver cr = context.getContentResolver();
900 item.onAddToDatabase(context, values);
902 item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
903 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
905 values.put(LauncherSettings.Favorites._ID, item.id);
907 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
908 Runnable r = new Runnable() {
910 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
912 // Lock on mBgLock *after* the db operation
913 synchronized (sBgLock) {
914 checkItemInfoLocked(item.id, item, stackTrace);
915 sBgItemsIdMap.put(item.id, item);
916 switch (item.itemType) {
917 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
918 sBgFolders.put(item.id, (FolderInfo) item);
920 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
921 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
922 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
923 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
924 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
925 sBgWorkspaceItems.add(item);
927 if (!sBgFolders.containsKey(item.container)) {
928 // Adding an item to a folder that doesn't exist.
929 String msg = "adding item: " + item + " to a folder that " +
934 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
935 incrementPinnedShortcutCount(
936 ShortcutKey.fromShortcutInfo((ShortcutInfo) item),
937 true /* shouldPin */);
940 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
941 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
947 runOnWorkerThread(r);
950 private static ArrayList<ItemInfo> getItemsByPackageName(
951 final String pn, final UserHandleCompat user) {
952 ItemInfoFilter filter = new ItemInfoFilter() {
954 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
955 return cn.getPackageName().equals(pn) && info.user.equals(user);
958 return filterItemInfos(sBgItemsIdMap, filter);
962 * Removes all the items from the database corresponding to the specified package.
964 static void deletePackageFromDatabase(Context context, final String pn,
965 final UserHandleCompat user) {
966 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
970 * Removes the specified item from the database
972 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
973 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
975 deleteItemsFromDatabase(context, items);
979 * Removes the specified items from the database
981 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
982 final ContentResolver cr = context.getContentResolver();
983 Runnable r = new Runnable() {
985 for (ItemInfo item : items) {
986 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
987 cr.delete(uri, null, null);
989 // Lock on mBgLock *after* the db operation
990 synchronized (sBgLock) {
991 switch (item.itemType) {
992 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
993 sBgFolders.remove(item.id);
994 for (ItemInfo info: sBgItemsIdMap) {
995 if (info.container == item.id) {
996 // We are deleting a folder which still contains items that
997 // think they are contained by that folder.
998 String msg = "deleting a folder (" + item + ") which still " +
999 "contains items (" + info + ")";
1003 sBgWorkspaceItems.remove(item);
1005 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1006 decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo(
1007 (ShortcutInfo) item));
1009 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1010 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1011 sBgWorkspaceItems.remove(item);
1013 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1014 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1017 sBgItemsIdMap.remove(item.id);
1022 runOnWorkerThread(r);
1026 * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
1028 private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
1029 synchronized (sBgLock) {
1030 MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
1031 if (count == null || --count.value == 0) {
1032 LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
1038 * Increment the count for the given shortcut, pinning it if the count becomes 1.
1040 * As an optimization, the caller can pass shouldPin == false to avoid
1041 * unnecessary RPC's if the shortcut is already pinned.
1043 private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
1044 synchronized (sBgLock) {
1045 MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
1046 if (count == null) {
1047 count = new MutableInt(1);
1048 sBgPinnedShortcutCounts.put(pinnedShortcut, count);
1052 if (shouldPin && count.value == 1) {
1053 LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
1059 * Update the order of the workspace screens in the database. The array list contains
1060 * a list of screen ids in the order that they should appear.
1062 public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1063 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1064 final ContentResolver cr = context.getContentResolver();
1065 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1067 // Remove any negative screen ids -- these aren't persisted
1068 Iterator<Long> iter = screensCopy.iterator();
1069 while (iter.hasNext()) {
1070 long id = iter.next();
1076 Runnable r = new Runnable() {
1079 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1081 ops.add(ContentProviderOperation.newDelete(uri).build());
1082 int count = screensCopy.size();
1083 for (int i = 0; i < count; i++) {
1084 ContentValues v = new ContentValues();
1085 long screenId = screensCopy.get(i);
1086 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1087 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1088 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1092 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1093 } catch (Exception ex) {
1094 throw new RuntimeException(ex);
1097 synchronized (sBgLock) {
1098 sBgWorkspaceScreens.clear();
1099 sBgWorkspaceScreens.addAll(screensCopy);
1103 runOnWorkerThread(r);
1107 * Remove the specified folder and all its contents from the database.
1109 public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
1110 final ContentResolver cr = context.getContentResolver();
1112 Runnable r = new Runnable() {
1114 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1115 // Lock on mBgLock *after* the db operation
1116 synchronized (sBgLock) {
1117 sBgItemsIdMap.remove(info.id);
1118 sBgFolders.remove(info.id);
1119 sBgWorkspaceItems.remove(info);
1122 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1123 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1124 // Lock on mBgLock *after* the db operation
1125 synchronized (sBgLock) {
1126 for (ItemInfo childInfo : info.contents) {
1127 sBgItemsIdMap.remove(childInfo.id);
1132 runOnWorkerThread(r);
1136 * Set this as the current Launcher activity object for the loader.
1138 public void initialize(Callbacks callbacks) {
1139 synchronized (mLock) {
1140 Preconditions.assertUIThread();
1141 // Remove any queued UI runnables
1142 mHandler.cancelAll();
1143 mCallbacks = new WeakReference<>(callbacks);
1148 public void onPackageChanged(String packageName, UserHandleCompat user) {
1149 int op = PackageUpdatedTask.OP_UPDATE;
1150 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1155 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1156 int op = PackageUpdatedTask.OP_REMOVE;
1157 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1162 public void onPackageAdded(String packageName, UserHandleCompat user) {
1163 int op = PackageUpdatedTask.OP_ADD;
1164 enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1169 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1170 boolean replacing) {
1171 enqueueItemUpdatedTask(
1172 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
1176 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1177 boolean replacing) {
1179 enqueueItemUpdatedTask(new PackageUpdatedTask(
1180 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1186 public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
1187 enqueueItemUpdatedTask(new PackageUpdatedTask(
1188 PackageUpdatedTask.OP_SUSPEND, packageNames,
1193 public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
1194 enqueueItemUpdatedTask(new PackageUpdatedTask(
1195 PackageUpdatedTask.OP_UNSUSPEND, packageNames,
1200 public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
1201 UserHandleCompat user) {
1202 enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
1205 public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
1206 UserHandleCompat user) {
1207 enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
1211 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1212 * ACTION_PACKAGE_CHANGED.
1215 public void onReceive(Context context, Intent intent) {
1216 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1218 final String action = intent.getAction();
1219 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1220 // If we have changed locale we need to clear out the labels in all apps/workspace.
1222 } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1223 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1224 UserManagerCompat.getInstance(context).enableAndResetCache();
1226 } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
1227 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
1228 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
1229 UserHandleCompat user = UserHandleCompat.fromIntent(intent);
1231 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
1232 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
1233 enqueueItemUpdatedTask(new PackageUpdatedTask(
1234 PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
1235 new String[0], user));
1238 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
1239 // we need to run the state change task again.
1240 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
1241 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
1242 enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
1245 } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
1246 ExtractionUtils.startColorExtractionServiceIfNecessary(context);
1250 void forceReload() {
1251 resetLoadedState(true, true);
1253 // Do this here because if the launcher activity is running it will be restarted.
1254 // If it's not running startLoaderFromBackground will merely tell it that it needs
1256 startLoaderFromBackground();
1259 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1260 synchronized (mLock) {
1261 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1262 // mWorkspaceLoaded to true later
1264 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1265 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1266 // Always reset deep shortcuts loaded.
1268 mDeepShortcutsLoaded = false;
1273 * When the launcher is in the background, it's possible for it to miss paired
1274 * configuration changes. So whenever we trigger the loader from the background
1275 * tell the launcher that it needs to re-run the loader when it comes back instead
1278 public void startLoaderFromBackground() {
1279 Callbacks callbacks = getCallback();
1280 if (callbacks != null) {
1281 // Only actually run the loader if they're not paused.
1282 if (!callbacks.setLoadOnResume()) {
1283 startLoader(callbacks.getCurrentWorkspaceScreen());
1289 * If there is already a loader task running, tell it to stop.
1291 private void stopLoaderLocked() {
1292 LoaderTask oldTask = mLoaderTask;
1293 if (oldTask != null) {
1294 oldTask.stopLocked();
1298 public boolean isCurrentCallbacks(Callbacks callbacks) {
1299 return (mCallbacks != null && mCallbacks.get() == callbacks);
1303 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
1304 * @return true if the page could be bound synchronously.
1306 public boolean startLoader(int synchronousBindPage) {
1307 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1308 InstallShortcutReceiver.enableInstallQueue();
1309 synchronized (mLock) {
1310 // Don't bother to start the thread if we know it's not going to do anything
1311 if (mCallbacks != null && mCallbacks.get() != null) {
1312 final Callbacks oldCallbacks = mCallbacks.get();
1313 // Clear any pending bind-runnables from the synchronized load process.
1314 runOnMainThread(new Runnable() {
1316 oldCallbacks.clearPendingBinds();
1320 // If there is already one running, tell it to stop.
1322 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
1323 // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
1324 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
1325 && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
1326 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1329 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1330 sWorker.post(mLoaderTask);
1337 public void stopLoader() {
1338 synchronized (mLock) {
1339 if (mLoaderTask != null) {
1340 mLoaderTask.stopLocked();
1346 * Loads the workspace screen ids in an ordered list.
1348 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1349 final ContentResolver contentResolver = context.getContentResolver();
1350 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1352 // Get screens ordered by rank.
1353 return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
1354 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
1358 * Runnable for the thread that loads the contents of the launcher:
1362 * - deep shortcuts within apps
1364 private class LoaderTask implements Runnable {
1365 private Context mContext;
1366 private int mPageToBindFirst;
1368 @Thunk boolean mIsLoadingAndBindingWorkspace;
1369 private boolean mStopped;
1370 @Thunk boolean mLoadAndBindStepFinished;
1372 LoaderTask(Context context, int pageToBindFirst) {
1374 mPageToBindFirst = pageToBindFirst;
1377 private void loadAndBindWorkspace() {
1378 mIsLoadingAndBindingWorkspace = true;
1380 // Load the workspace
1381 if (DEBUG_LOADERS) {
1382 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1385 if (!mWorkspaceLoaded) {
1387 synchronized (LoaderTask.this) {
1391 mWorkspaceLoaded = true;
1395 // Bind the workspace
1396 bindWorkspace(mPageToBindFirst);
1399 private void waitForIdle() {
1400 // Wait until the either we're stopped or the other threads are done.
1401 // This way we don't start loading all apps until the workspace has settled
1403 synchronized (LoaderTask.this) {
1404 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1406 mHandler.postIdle(new Runnable() {
1408 synchronized (LoaderTask.this) {
1409 mLoadAndBindStepFinished = true;
1410 if (DEBUG_LOADERS) {
1411 Log.d(TAG, "done with previous binding step");
1413 LoaderTask.this.notify();
1418 while (!mStopped && !mLoadAndBindStepFinished) {
1420 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1421 // wait no longer than 1sec at a time
1423 } catch (InterruptedException ex) {
1427 if (DEBUG_LOADERS) {
1428 Log.d(TAG, "waited "
1429 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1430 + "ms for previous step to finish binding");
1435 void runBindSynchronousPage(int synchronousBindPage) {
1436 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1437 // Ensure that we have a valid page index to load synchronously
1438 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1439 "valid page index");
1441 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1442 // Ensure that we don't try and bind a specified page when the pages have not been
1443 // loaded already (we should load everything asynchronously in that case)
1444 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1446 synchronized (mLock) {
1447 if (mIsLoaderTaskRunning) {
1448 // Ensure that we are never running the background loading at this point since
1449 // we also touch the background collections
1450 throw new RuntimeException("Error! Background loading is already running");
1454 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1455 // data structures, we can't allow any other thread to touch that data, but because
1456 // this call is synchronous, we can get away with not locking).
1458 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1459 // operations from the previous activity. We need to ensure that all queued operations
1460 // are executed before any synchronous binding work is done.
1463 // Divide the set of loaded items into those that we are binding synchronously, and
1464 // everything else that is to be bound normally (asynchronously).
1465 bindWorkspace(synchronousBindPage);
1466 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1470 bindDeepShortcuts();
1474 synchronized (mLock) {
1478 mIsLoaderTaskRunning = true;
1480 // Optimize for end-user experience: if the Launcher is up and // running with the
1481 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1482 // workspace first (default).
1484 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1485 loadAndBindWorkspace();
1494 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1495 loadAndBindAllApps();
1500 if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
1501 loadAndBindDeepShortcuts();
1504 // Clear out this reference, otherwise we end up holding it until all of the
1505 // callback runnables are done.
1508 synchronized (mLock) {
1509 // If we are still the last one to be scheduled, remove ourselves.
1510 if (mLoaderTask == this) {
1513 mIsLoaderTaskRunning = false;
1514 mHasLoaderCompletedOnce = true;
1518 public void stopLocked() {
1519 synchronized (LoaderTask.this) {
1526 * Gets the callbacks object. If we've been stopped, or if the launcher object
1527 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1528 * object that was around when the deferred message was scheduled, and if there's
1529 * a new Callbacks object around then also return null. This will save us from
1530 * calling onto it with data that will be ignored.
1532 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1533 synchronized (mLock) {
1538 if (mCallbacks == null) {
1542 final Callbacks callbacks = mCallbacks.get();
1543 if (callbacks != oldCallbacks) {
1546 if (callbacks == null) {
1547 Log.w(TAG, "no mCallbacks");
1555 // check & update map of what's occupied; used to discard overlapping/invalid items
1556 private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
1557 ArrayList<Long> workspaceScreens) {
1558 LauncherAppState app = LauncherAppState.getInstance();
1559 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1561 long containerIndex = item.screenId;
1562 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1563 // Return early if we detect that an item is under the hotseat button
1564 if (!FeatureFlags.NO_ALL_APPS_ICON &&
1565 profile.isAllAppsButtonRank((int) item.screenId)) {
1566 Log.e(TAG, "Error loading shortcut into hotseat " + item
1567 + " into position (" + item.screenId + ":" + item.cellX + ","
1568 + item.cellY + ") occupied by all apps");
1572 final GridOccupancy hotseatOccupancy =
1573 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1575 if (item.screenId >= profile.numHotseatIcons) {
1576 Log.e(TAG, "Error loading shortcut " + item
1577 + " into hotseat position " + item.screenId
1578 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1583 if (hotseatOccupancy != null) {
1584 if (hotseatOccupancy.cells[(int) item.screenId][0]) {
1585 Log.e(TAG, "Error loading shortcut into hotseat " + item
1586 + " into position (" + item.screenId + ":" + item.cellX + ","
1587 + item.cellY + ") already occupied");
1590 hotseatOccupancy.cells[(int) item.screenId][0] = true;
1594 final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
1595 occupancy.cells[(int) item.screenId][0] = true;
1596 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
1599 } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1600 if (!workspaceScreens.contains((Long) item.screenId)) {
1601 // The item has an invalid screen id.
1605 // Skip further checking if it is not the hotseat or workspace container
1609 final int countX = profile.numColumns;
1610 final int countY = profile.numRows;
1611 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1612 item.cellX < 0 || item.cellY < 0 ||
1613 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1614 Log.e(TAG, "Error loading shortcut " + item
1615 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1616 + item.cellX + "," + item.cellY
1617 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1621 if (!occupied.containsKey(item.screenId)) {
1622 GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
1623 if (item.screenId == Workspace.FIRST_SCREEN_ID) {
1624 // Mark the first row as occupied (if the feature is enabled)
1625 // in order to account for the QSB.
1626 screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
1628 occupied.put(item.screenId, screen);
1630 final GridOccupancy occupancy = occupied.get(item.screenId);
1632 // Check if any workspace icons overlap with each other
1633 if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
1634 occupancy.markCells(item, true);
1637 Log.e(TAG, "Error loading shortcut " + item
1638 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1639 + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
1640 + ") already occupied");
1645 /** Clears all the sBg data structures */
1646 private void clearSBgDataStructures() {
1647 synchronized (sBgLock) {
1648 sBgWorkspaceItems.clear();
1649 sBgAppWidgets.clear();
1651 sBgItemsIdMap.clear();
1652 sBgWorkspaceScreens.clear();
1653 sBgPinnedShortcutCounts.clear();
1657 private void loadWorkspace() {
1658 if (LauncherAppState.PROFILE_STARTUP) {
1659 Trace.beginSection("Loading Workspace");
1661 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1663 final Context context = mContext;
1664 final ContentResolver contentResolver = context.getContentResolver();
1665 final PackageManager manager = context.getPackageManager();
1666 final boolean isSafeMode = manager.isSafeMode();
1667 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1668 final boolean isSdCardReady = Utilities.isBootCompleted();
1670 LauncherAppState app = LauncherAppState.getInstance();
1671 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1672 int countX = profile.numColumns;
1673 int countY = profile.numRows;
1675 boolean clearDb = false;
1677 ImportDataTask.performImportIfPossible(context);
1678 } catch (Exception e) {
1679 // Migration failed. Clear workspace.
1683 if (!clearDb && GridSizeMigrationTask.ENABLED &&
1684 !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
1685 // Migration failed. Clear workspace.
1690 Log.d(TAG, "loadWorkspace: resetting launcher database");
1691 LauncherSettings.Settings.call(contentResolver,
1692 LauncherSettings.Settings.METHOD_DELETE_DB);
1695 Log.d(TAG, "loadWorkspace: loading default favorites");
1696 LauncherSettings.Settings.call(contentResolver,
1697 LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
1699 synchronized (sBgLock) {
1700 clearSBgDataStructures();
1701 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1702 .getInstance(mContext).updateAndGetActiveSessionCache();
1703 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
1705 final ArrayList<Long> itemsToRemove = new ArrayList<>();
1706 final ArrayList<Long> restoredRows = new ArrayList<>();
1707 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
1708 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1709 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1710 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1712 // +1 for the hotseat (it can be larger than the workspace)
1713 // Load workspace in reverse order to ensure that latest items are loaded first (and
1714 // before any earlier duplicates)
1715 final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
1716 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
1719 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1720 final int intentIndex = c.getColumnIndexOrThrow
1721 (LauncherSettings.Favorites.INTENT);
1722 final int containerIndex = c.getColumnIndexOrThrow(
1723 LauncherSettings.Favorites.CONTAINER);
1724 final int itemTypeIndex = c.getColumnIndexOrThrow(
1725 LauncherSettings.Favorites.ITEM_TYPE);
1726 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1727 LauncherSettings.Favorites.APPWIDGET_ID);
1728 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1729 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1730 final int screenIndex = c.getColumnIndexOrThrow(
1731 LauncherSettings.Favorites.SCREEN);
1732 final int cellXIndex = c.getColumnIndexOrThrow
1733 (LauncherSettings.Favorites.CELLX);
1734 final int cellYIndex = c.getColumnIndexOrThrow
1735 (LauncherSettings.Favorites.CELLY);
1736 final int spanXIndex = c.getColumnIndexOrThrow
1737 (LauncherSettings.Favorites.SPANX);
1738 final int spanYIndex = c.getColumnIndexOrThrow(
1739 LauncherSettings.Favorites.SPANY);
1740 final int rankIndex = c.getColumnIndexOrThrow(
1741 LauncherSettings.Favorites.RANK);
1742 final int restoredIndex = c.getColumnIndexOrThrow(
1743 LauncherSettings.Favorites.RESTORED);
1744 final int profileIdIndex = c.getColumnIndexOrThrow(
1745 LauncherSettings.Favorites.PROFILE_ID);
1746 final int optionsIndex = c.getColumnIndexOrThrow(
1747 LauncherSettings.Favorites.OPTIONS);
1748 final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
1750 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1751 final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
1752 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
1753 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1754 long serialNo = mUserManager.getSerialNumberForUser(user);
1755 allUsers.put(serialNo, user);
1756 quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
1758 boolean userUnlocked = mUserManager.isUserUnlocked(user);
1760 // We can only query for shortcuts when the user is unlocked.
1762 List<ShortcutInfoCompat> pinnedShortcuts =
1763 mDeepShortcutManager.queryForPinnedShortcuts(null, user);
1764 if (mDeepShortcutManager.wasLastCallSuccess()) {
1765 for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
1766 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
1770 // Shortcut manager can fail due to some race condition when the
1771 // lock state changes too frequently. For the purpose of the loading
1772 // shortcuts, consider the user is still locked.
1773 userUnlocked = false;
1776 unlockedUsers.put(serialNo, userUnlocked);
1780 String intentDescription;
1781 LauncherAppWidgetInfo appWidgetInfo;
1786 UserHandleCompat user;
1787 String targetPackage;
1789 while (!mStopped && c.moveToNext()) {
1791 int itemType = c.getInt(itemTypeIndex);
1792 boolean restored = 0 != c.getInt(restoredIndex);
1793 boolean allowMissingTarget = false;
1794 container = c.getInt(containerIndex);
1797 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1798 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1799 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1800 id = c.getLong(idIndex);
1801 intentDescription = c.getString(intentIndex);
1802 serialNumber = c.getInt(profileIdIndex);
1803 user = allUsers.get(serialNumber);
1804 int promiseType = c.getInt(restoredIndex);
1805 int disabledState = 0;
1806 boolean itemReplaced = false;
1807 targetPackage = null;
1809 // User has been deleted remove the item.
1810 itemsToRemove.add(id);
1814 intent = Intent.parseUri(intentDescription, 0);
1815 ComponentName cn = intent.getComponent();
1816 if (cn != null && cn.getPackageName() != null) {
1817 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1818 cn.getPackageName(), user);
1819 boolean validComponent = validPkg &&
1820 launcherApps.isActivityEnabledForProfile(cn, user);
1822 targetPackage = cn.getPackageName();
1825 if (validComponent) {
1827 // no special handling necessary for this item
1828 restoredRows.add(id);
1831 if (quietMode.get(serialNumber)) {
1832 disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
1834 } else if (validPkg) {
1836 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1837 // We allow auto install apps to have their intent
1838 // updated after an install.
1839 intent = manager.getLaunchIntentForPackage(
1840 cn.getPackageName());
1841 if (intent != null) {
1842 ContentValues values = new ContentValues();
1843 values.put(LauncherSettings.Favorites.INTENT,
1845 updateItem(id, values);
1849 if (intent == null) {
1850 // The app is installed but the component is no
1851 // longer available.
1852 FileLog.d(TAG, "Invalid component removed: " + cn);
1853 itemsToRemove.add(id);
1856 // no special handling necessary for this item
1857 restoredRows.add(id);
1860 } else if (restored) {
1861 // Package is not yet available but might be
1863 FileLog.d(TAG, "package not yet restored: " + cn);
1865 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1866 // Restore has started once.
1867 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1868 // App restore has started. Update the flag
1869 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1870 ContentValues values = new ContentValues();
1871 values.put(LauncherSettings.Favorites.RESTORED,
1873 updateItem(id, values);
1874 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
1875 // This is a common app. Try to replace this.
1876 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
1877 CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
1878 if (parser.findDefaultApp()) {
1879 // Default app found. Replace it.
1880 intent = parser.parsedIntent;
1881 cn = intent.getComponent();
1882 ContentValues values = parser.parsedValues;
1883 values.put(LauncherSettings.Favorites.RESTORED, 0);
1884 updateItem(id, values);
1886 itemReplaced = true;
1889 FileLog.d(TAG, "Unrestored package removed: " + cn);
1890 itemsToRemove.add(id);
1894 FileLog.d(TAG, "Unrestored package removed: " + cn);
1895 itemsToRemove.add(id);
1898 } else if (PackageManagerHelper.isAppOnSdcard(
1899 manager, cn.getPackageName())) {
1900 // Package is present but not available.
1901 allowMissingTarget = true;
1902 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1903 } else if (!isSdCardReady) {
1904 // SdCard is not ready yet. Package might get available,
1905 // once it is ready.
1906 Log.d(TAG, "Invalid package: " + cn + " (check again later)");
1907 HashSet<String> pkgs = sPendingPackages.get(user);
1909 pkgs = new HashSet<String>();
1910 sPendingPackages.put(user, pkgs);
1912 pkgs.add(cn.getPackageName());
1913 allowMissingTarget = true;
1914 // Add the icon on the workspace anyway.
1917 // Do not wait for external media load anymore.
1918 // Log the invalid package, and remove it
1919 FileLog.d(TAG, "Invalid package removed: " + cn);
1920 itemsToRemove.add(id);
1923 } else if (cn == null) {
1924 // For shortcuts with no component, keep them as they are
1925 restoredRows.add(id);
1928 } catch (URISyntaxException e) {
1929 FileLog.d(TAG, "Invalid uri: " + intentDescription);
1930 itemsToRemove.add(id);
1934 boolean useLowResIcon = container >= 0 &&
1935 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1938 if (user.equals(UserHandleCompat.myUserHandle())) {
1939 info = getAppShortcutInfo(intent, user, null,
1940 cursorIconInfo, false, useLowResIcon);
1942 // Don't replace items for other profiles.
1943 itemsToRemove.add(id);
1946 } else if (restored) {
1947 if (user.equals(UserHandleCompat.myUserHandle())) {
1948 info = getRestoredItemInfo(c, intent,
1949 promiseType, itemType, cursorIconInfo);
1950 intent = getRestoredItemIntent(c, context, intent);
1952 // Don't restore items for other profiles.
1953 itemsToRemove.add(id);
1956 } else if (itemType ==
1957 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1958 info = getAppShortcutInfo(intent, user, c,
1959 cursorIconInfo, allowMissingTarget, useLowResIcon);
1960 } else if (itemType ==
1961 LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1963 ShortcutKey key = ShortcutKey.fromIntent(intent, user);
1964 if (unlockedUsers.get(serialNumber)) {
1965 ShortcutInfoCompat pinnedShortcut =
1966 shortcutKeyToPinnedShortcuts.get(key);
1967 if (pinnedShortcut == null) {
1968 // The shortcut is no longer valid.
1969 itemsToRemove.add(id);
1972 info = new ShortcutInfo(pinnedShortcut, context);
1973 intent = info.intent;
1975 // Create a shortcut info in disabled mode for now.
1976 info = new ShortcutInfo();
1978 info.itemType = itemType;
1979 loadInfoFromCursor(info, c, cursorIconInfo);
1981 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1983 incrementPinnedShortcutCount(key, false /* shouldPin */);
1984 } else { // item type == ITEM_TYPE_SHORTCUT
1985 info = getShortcutInfo(c, cursorIconInfo);
1987 // Shortcuts are only available on the primary profile
1988 if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
1989 disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1992 // App shortcuts that used to be automatically added to Launcher
1993 // didn't always have the correct intent flags set, so do that
1995 if (intent.getAction() != null &&
1996 intent.getCategories() != null &&
1997 intent.getAction().equals(Intent.ACTION_MAIN) &&
1998 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2000 Intent.FLAG_ACTIVITY_NEW_TASK |
2001 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2007 info.intent = intent;
2008 info.container = container;
2009 info.screenId = c.getInt(screenIndex);
2010 info.cellX = c.getInt(cellXIndex);
2011 info.cellY = c.getInt(cellYIndex);
2012 info.rank = c.getInt(rankIndex);
2015 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2016 if (info.promisedIntent != null) {
2017 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2019 info.isDisabled |= disabledState;
2020 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2021 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2024 // check & update map of what's occupied
2025 if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
2026 itemsToRemove.add(id);
2031 ComponentName cn = info.getTargetComponent();
2033 Integer progress = installingPkgs.get(cn.getPackageName());
2034 if (progress != null) {
2035 info.setInstallProgress(progress);
2037 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2042 switch (container) {
2043 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2044 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2045 sBgWorkspaceItems.add(info);
2048 // Item is in a user folder
2049 FolderInfo folderInfo =
2050 findOrMakeFolder(sBgFolders, container);
2051 folderInfo.add(info, false);
2054 sBgItemsIdMap.put(info.id, info);
2056 throw new RuntimeException("Unexpected null ShortcutInfo");
2060 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2061 id = c.getLong(idIndex);
2062 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2064 // Do not trim the folder label, as is was set by the user.
2065 folderInfo.title = c.getString(cursorIconInfo.titleIndex);
2067 folderInfo.container = container;
2068 folderInfo.screenId = c.getInt(screenIndex);
2069 folderInfo.cellX = c.getInt(cellXIndex);
2070 folderInfo.cellY = c.getInt(cellYIndex);
2071 folderInfo.spanX = 1;
2072 folderInfo.spanY = 1;
2073 folderInfo.options = c.getInt(optionsIndex);
2075 // check & update map of what's occupied
2076 if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
2077 itemsToRemove.add(id);
2081 switch (container) {
2082 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2083 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2084 sBgWorkspaceItems.add(folderInfo);
2089 // no special handling required for restored folders
2090 restoredRows.add(id);
2093 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2094 sBgFolders.put(folderInfo.id, folderInfo);
2097 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2098 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2099 // Read all Launcher-specific widget details
2100 boolean customWidget = itemType ==
2101 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2103 int appWidgetId = c.getInt(appWidgetIdIndex);
2104 serialNumber = c.getLong(profileIdIndex);
2105 String savedProvider = c.getString(appWidgetProviderIndex);
2106 id = c.getLong(idIndex);
2107 user = allUsers.get(serialNumber);
2109 itemsToRemove.add(id);
2113 final ComponentName component =
2114 ComponentName.unflattenFromString(savedProvider);
2116 final int restoreStatus = c.getInt(restoredIndex);
2117 final boolean isIdValid = (restoreStatus &
2118 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2119 final boolean wasProviderReady = (restoreStatus &
2120 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2122 if (widgetProvidersMap == null) {
2123 widgetProvidersMap = AppWidgetManagerCompat
2124 .getInstance(mContext).getAllProvidersMap();
2126 final AppWidgetProviderInfo provider = widgetProvidersMap.get(
2128 ComponentName.unflattenFromString(savedProvider),
2131 final boolean isProviderReady = isValidProvider(provider);
2132 if (!isSafeMode && !customWidget &&
2133 wasProviderReady && !isProviderReady) {
2134 FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
2136 itemsToRemove.add(id);
2138 if (isProviderReady) {
2139 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2142 // The provider is available. So the widget is either
2143 // available or not available. We do not need to track
2144 // any future restore updates.
2145 int status = restoreStatus &
2146 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2147 if (!wasProviderReady) {
2148 // If provider was not previously ready, update the
2149 // status and UI flag.
2151 // Id would be valid only if the widget restore broadcast was received.
2153 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
2155 status &= ~LauncherAppWidgetInfo
2156 .FLAG_PROVIDER_NOT_READY;
2159 appWidgetInfo.restoreStatus = status;
2161 Log.v(TAG, "Widget restore pending id=" + id
2162 + " appWidgetId=" + appWidgetId
2163 + " status =" + restoreStatus);
2164 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2166 appWidgetInfo.restoreStatus = restoreStatus;
2167 Integer installProgress = installingPkgs.get(component.getPackageName());
2169 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
2170 // Restore has started once.
2171 } else if (installProgress != null) {
2172 // App restore has started. Update the flag
2173 appWidgetInfo.restoreStatus |=
2174 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2175 } else if (!isSafeMode) {
2176 FileLog.d(TAG, "Unrestored widget removed: " + component);
2177 itemsToRemove.add(id);
2181 appWidgetInfo.installProgress =
2182 installProgress == null ? 0 : installProgress;
2184 if (appWidgetInfo.hasRestoreFlag(
2185 LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
2186 intentDescription = c.getString(intentIndex);
2187 if (!TextUtils.isEmpty(intentDescription)) {
2188 appWidgetInfo.bindOptions =
2189 Intent.parseUri(intentDescription, 0);
2193 appWidgetInfo.id = id;
2194 appWidgetInfo.screenId = c.getInt(screenIndex);
2195 appWidgetInfo.cellX = c.getInt(cellXIndex);
2196 appWidgetInfo.cellY = c.getInt(cellYIndex);
2197 appWidgetInfo.spanX = c.getInt(spanXIndex);
2198 appWidgetInfo.spanY = c.getInt(spanYIndex);
2199 appWidgetInfo.user = user;
2201 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2202 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2203 Log.e(TAG, "Widget found where container != " +
2204 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2205 itemsToRemove.add(id);
2209 appWidgetInfo.container = container;
2210 // check & update map of what's occupied
2211 if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
2212 itemsToRemove.add(id);
2216 if (!customWidget) {
2217 String providerName =
2218 appWidgetInfo.providerName.flattenToString();
2219 if (!providerName.equals(savedProvider) ||
2220 (appWidgetInfo.restoreStatus != restoreStatus)) {
2221 ContentValues values = new ContentValues();
2223 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2225 values.put(LauncherSettings.Favorites.RESTORED,
2226 appWidgetInfo.restoreStatus);
2227 updateItem(id, values);
2230 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2231 sBgAppWidgets.add(appWidgetInfo);
2235 } catch (Exception e) {
2236 Log.e(TAG, "Desktop items loading interrupted", e);
2240 Utilities.closeSilently(c);
2243 // Break early if we've stopped loading
2245 clearSBgDataStructures();
2249 if (itemsToRemove.size() > 0) {
2250 // Remove dead items
2251 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2252 Utilities.createDbSelectionQuery(
2253 LauncherSettings.Favorites._ID, itemsToRemove), null);
2254 if (DEBUG_LOADERS) {
2255 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2256 LauncherSettings.Favorites._ID, itemsToRemove));
2259 // Remove any empty folder
2260 ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
2261 .call(contentResolver,
2262 LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
2263 .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
2264 for (long folderId : deletedFolderIds) {
2265 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2266 sBgFolders.remove(folderId);
2267 sBgItemsIdMap.remove(folderId);
2271 // Unpin shortcuts that don't exist on the workspace.
2272 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
2273 MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
2274 if (numTimesPinned == null || numTimesPinned.value == 0) {
2275 // Shortcut is pinned but doesn't exist on the workspace; unpin it.
2276 mDeepShortcutManager.unpinShortcut(key);
2280 // Sort all the folder items and make sure the first 3 items are high resolution.
2281 for (FolderInfo folder : sBgFolders) {
2282 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
2284 for (ShortcutInfo info : folder.contents) {
2285 if (info.usingLowResIcon) {
2286 info.updateIcon(mIconCache, false);
2289 if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
2295 if (restoredRows.size() > 0) {
2296 // Update restored items that no longer require special handling
2297 ContentValues values = new ContentValues();
2298 values.put(LauncherSettings.Favorites.RESTORED, 0);
2299 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2300 Utilities.createDbSelectionQuery(
2301 LauncherSettings.Favorites._ID, restoredRows), null);
2304 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2305 context.registerReceiver(new AppsAvailabilityCheck(),
2306 new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
2310 // Remove any empty screens
2311 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2312 for (ItemInfo item: sBgItemsIdMap) {
2313 long screenId = item.screenId;
2314 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2315 unusedScreens.contains(screenId)) {
2316 unusedScreens.remove(screenId);
2320 // If there are any empty screens remove them, and update.
2321 if (unusedScreens.size() != 0) {
2322 sBgWorkspaceScreens.removeAll(unusedScreens);
2323 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2326 if (DEBUG_LOADERS) {
2327 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2328 Log.d(TAG, "workspace layout: ");
2329 int nScreens = occupied.size();
2330 for (int y = 0; y < countY; y++) {
2333 for (int i = 0; i < nScreens; i++) {
2334 long screenId = occupied.keyAt(i);
2339 Log.d(TAG, "[ " + line + " ]");
2343 if (LauncherAppState.PROFILE_STARTUP) {
2349 * Partially updates the item without any notification. Must be called on the worker thread.
2351 private void updateItem(long itemId, ContentValues update) {
2352 mContext.getContentResolver().update(
2353 LauncherSettings.Favorites.CONTENT_URI,
2355 BaseColumns._ID + "= ?",
2356 new String[]{Long.toString(itemId)});
2359 /** Filters the set of items who are directly or indirectly (via another container) on the
2360 * specified screen. */
2361 private void filterCurrentWorkspaceItems(long currentScreenId,
2362 ArrayList<ItemInfo> allWorkspaceItems,
2363 ArrayList<ItemInfo> currentScreenItems,
2364 ArrayList<ItemInfo> otherScreenItems) {
2365 // Purge any null ItemInfos
2366 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2367 while (iter.hasNext()) {
2368 ItemInfo i = iter.next();
2374 // Order the set of items by their containers first, this allows use to walk through the
2375 // list sequentially, build up a list of containers that are in the specified screen,
2376 // as well as all items in those containers.
2377 Set<Long> itemsOnScreen = new HashSet<Long>();
2378 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2380 public int compare(ItemInfo lhs, ItemInfo rhs) {
2381 return Utilities.longCompare(lhs.container, rhs.container);
2384 for (ItemInfo info : allWorkspaceItems) {
2385 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2386 if (info.screenId == currentScreenId) {
2387 currentScreenItems.add(info);
2388 itemsOnScreen.add(info.id);
2390 otherScreenItems.add(info);
2392 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2393 currentScreenItems.add(info);
2394 itemsOnScreen.add(info.id);
2396 if (itemsOnScreen.contains(info.container)) {
2397 currentScreenItems.add(info);
2398 itemsOnScreen.add(info.id);
2400 otherScreenItems.add(info);
2406 /** Filters the set of widgets which are on the specified screen. */
2407 private void filterCurrentAppWidgets(long currentScreenId,
2408 ArrayList<LauncherAppWidgetInfo> appWidgets,
2409 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2410 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2412 for (LauncherAppWidgetInfo widget : appWidgets) {
2413 if (widget == null) continue;
2414 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2415 widget.screenId == currentScreenId) {
2416 currentScreenWidgets.add(widget);
2418 otherScreenWidgets.add(widget);
2423 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2425 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2426 final LauncherAppState app = LauncherAppState.getInstance();
2427 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2428 final int screenCols = profile.numColumns;
2429 final int screenCellCount = profile.numColumns * profile.numRows;
2430 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2432 public int compare(ItemInfo lhs, ItemInfo rhs) {
2433 if (lhs.container == rhs.container) {
2434 // Within containers, order by their spatial position in that container
2435 switch ((int) lhs.container) {
2436 case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
2437 long lr = (lhs.screenId * screenCellCount +
2438 lhs.cellY * screenCols + lhs.cellX);
2439 long rr = (rhs.screenId * screenCellCount +
2440 rhs.cellY * screenCols + rhs.cellX);
2441 return Utilities.longCompare(lr, rr);
2443 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
2444 // We currently use the screen id as the rank
2445 return Utilities.longCompare(lhs.screenId, rhs.screenId);
2448 if (ProviderConfig.IS_DOGFOOD_BUILD) {
2449 throw new RuntimeException("Unexpected container type when " +
2450 "sorting workspace items.");
2455 // Between containers, order by hotseat, desktop
2456 return Utilities.longCompare(lhs.container, rhs.container);
2462 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2463 final ArrayList<Long> orderedScreens) {
2464 final Runnable r = new Runnable() {
2467 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2468 if (callbacks != null) {
2469 callbacks.bindScreens(orderedScreens);
2476 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2477 final ArrayList<ItemInfo> workspaceItems,
2478 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2479 final Executor executor) {
2481 // Bind the workspace items
2482 int N = workspaceItems.size();
2483 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2484 final int start = i;
2485 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2486 final Runnable r = new Runnable() {
2489 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2490 if (callbacks != null) {
2491 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2496 executor.execute(r);
2499 // Bind the widgets, one at a time
2500 N = appWidgets.size();
2501 for (int i = 0; i < N; i++) {
2502 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2503 final Runnable r = new Runnable() {
2505 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2506 if (callbacks != null) {
2507 callbacks.bindAppWidget(widget);
2511 executor.execute(r);
2516 * Binds all loaded data to actual views on the main thread.
2518 private void bindWorkspace(int synchronizeBindPage) {
2519 final long t = SystemClock.uptimeMillis();
2522 // Don't use these two variables in any of the callback runnables.
2523 // Otherwise we hold a reference to them.
2524 final Callbacks oldCallbacks = mCallbacks.get();
2525 if (oldCallbacks == null) {
2526 // This launcher has exited and nobody bothered to tell us. Just bail.
2527 Log.w(TAG, "LoaderTask running with no launcher");
2531 // Save a copy of all the bg-thread collections
2532 ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
2533 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
2534 ArrayList<Long> orderedScreenIds = new ArrayList<>();
2536 synchronized (sBgLock) {
2537 workspaceItems.addAll(sBgWorkspaceItems);
2538 appWidgets.addAll(sBgAppWidgets);
2539 orderedScreenIds.addAll(sBgWorkspaceScreens);
2542 final int currentScreen;
2544 int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
2545 ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
2546 if (currScreen >= orderedScreenIds.size()) {
2547 // There may be no workspace screens (just hotseat items and an empty page).
2548 currScreen = PagedView.INVALID_RESTORE_PAGE;
2550 currentScreen = currScreen;
2552 final boolean validFirstPage = currentScreen >= 0;
2553 final long currentScreenId =
2554 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
2556 // Separate the items that are on the current screen, and all the other remaining items
2557 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
2558 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
2559 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
2560 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
2562 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2563 otherWorkspaceItems);
2564 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2566 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2567 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2569 // Tell the workspace that we're about to start binding items
2570 r = new Runnable() {
2572 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2573 if (callbacks != null) {
2574 callbacks.clearPendingBinds();
2575 callbacks.startBinding();
2581 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2583 Executor mainExecutor = new DeferredMainThreadExecutor();
2584 // Load items on the current page.
2585 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
2587 // In case of validFirstPage, only bind the first screen, and defer binding the
2588 // remaining screens after first onDraw (and an optional the fade animation whichever
2590 // This ensures that the first screen is immediately visible (eg. during rotation)
2591 // In case of !validFirstPage, bind all pages one after other.
2592 final Executor deferredExecutor =
2593 validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
2595 mainExecutor.execute(new Runnable() {
2598 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2599 if (callbacks != null) {
2600 callbacks.finishFirstPageBind(
2601 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
2606 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
2608 // Tell the workspace that we're done binding items
2609 r = new Runnable() {
2611 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2612 if (callbacks != null) {
2613 callbacks.finishBindingItems();
2616 mIsLoadingAndBindingWorkspace = false;
2618 // Run all the bind complete runnables after workspace is bound.
2619 if (!mBindCompleteRunnables.isEmpty()) {
2620 synchronized (mBindCompleteRunnables) {
2621 for (final Runnable r : mBindCompleteRunnables) {
2622 runOnWorkerThread(r);
2624 mBindCompleteRunnables.clear();
2628 // If we're profiling, ensure this is the last thing in the queue.
2629 if (DEBUG_LOADERS) {
2630 Log.d(TAG, "bound workspace in "
2631 + (SystemClock.uptimeMillis()-t) + "ms");
2636 deferredExecutor.execute(r);
2638 if (validFirstPage) {
2639 r = new Runnable() {
2641 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2642 if (callbacks != null) {
2643 // We are loading synchronously, which means, some of the pages will be
2644 // bound after first draw. Inform the callbacks that page binding is
2645 // not complete, and schedule the remaining pages.
2646 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2647 callbacks.onPageBoundSynchronously(currentScreen);
2649 callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
2657 private void loadAndBindAllApps() {
2658 if (DEBUG_LOADERS) {
2659 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2661 if (!mAllAppsLoaded) {
2663 synchronized (LoaderTask.this) {
2669 synchronized (LoaderTask.this) {
2673 mAllAppsLoaded = true;
2680 private void updateIconCache() {
2681 // Ignore packages which have a promise icon.
2682 HashSet<String> packagesToIgnore = new HashSet<>();
2683 synchronized (sBgLock) {
2684 for (ItemInfo info : sBgItemsIdMap) {
2685 if (info instanceof ShortcutInfo) {
2686 ShortcutInfo si = (ShortcutInfo) info;
2687 if (si.isPromise() && si.getTargetComponent() != null) {
2688 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2690 } else if (info instanceof LauncherAppWidgetInfo) {
2691 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2692 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2693 packagesToIgnore.add(lawi.providerName.getPackageName());
2698 mIconCache.updateDbIcons(packagesToIgnore);
2701 private void onlyBindAllApps() {
2702 final Callbacks oldCallbacks = mCallbacks.get();
2703 if (oldCallbacks == null) {
2704 // This launcher has exited and nobody bothered to tell us. Just bail.
2705 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2710 @SuppressWarnings("unchecked")
2711 final ArrayList<AppInfo> list
2712 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2713 Runnable r = new Runnable() {
2715 final long t = SystemClock.uptimeMillis();
2716 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2717 if (callbacks != null) {
2718 callbacks.bindAllApplications(list);
2720 if (DEBUG_LOADERS) {
2721 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2722 + (SystemClock.uptimeMillis() - t) + "ms");
2729 private void loadAllApps() {
2730 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2732 final Callbacks oldCallbacks = mCallbacks.get();
2733 if (oldCallbacks == null) {
2734 // This launcher has exited and nobody bothered to tell us. Just bail.
2735 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2739 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2741 // Clear the list of apps
2742 mBgAllAppsList.clear();
2743 for (UserHandleCompat user : profiles) {
2744 // Query for the set of apps
2745 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2746 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2747 if (DEBUG_LOADERS) {
2748 Log.d(TAG, "getActivityList took "
2749 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2750 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2752 // Fail if we don't have any apps
2753 // TODO: Fix this. Only fail for the current user.
2754 if (apps == null || apps.isEmpty()) {
2757 boolean quietMode = mUserManager.isQuietModeEnabled(user);
2758 // Create the ApplicationInfos
2759 for (int i = 0; i < apps.size(); i++) {
2760 LauncherActivityInfoCompat app = apps.get(i);
2761 // This builds the icon bitmaps.
2762 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
2765 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2766 if (heuristic != null) {
2767 final Runnable r = new Runnable() {
2771 heuristic.processUserApps(apps);
2774 runOnMainThread(new Runnable() {
2778 // Check isLoadingWorkspace on the UI thread, as it is updated on
2780 if (mIsLoadingAndBindingWorkspace) {
2781 synchronized (mBindCompleteRunnables) {
2782 mBindCompleteRunnables.add(r);
2785 runOnWorkerThread(r);
2791 // Huh? Shouldn't this be inside the Runnable below?
2792 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2793 mBgAllAppsList.added = new ArrayList<AppInfo>();
2795 // Post callback on main thread
2796 mHandler.post(new Runnable() {
2799 final long bindTime = SystemClock.uptimeMillis();
2800 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2801 if (callbacks != null) {
2802 callbacks.bindAllApplications(added);
2803 if (DEBUG_LOADERS) {
2804 Log.d(TAG, "bound " + added.size() + " apps in "
2805 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2808 Log.i(TAG, "not binding apps: no Launcher activity");
2812 // Cleanup any data stored for a deleted user.
2813 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2814 if (DEBUG_LOADERS) {
2815 Log.d(TAG, "Icons processed in "
2816 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2820 private void loadAndBindDeepShortcuts() {
2821 if (DEBUG_LOADERS) {
2822 Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
2824 if (!mDeepShortcutsLoaded) {
2825 mBgDeepShortcutMap.clear();
2826 mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
2827 if (mHasShortcutHostPermission) {
2828 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
2829 if (mUserManager.isUserUnlocked(user)) {
2830 List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
2831 .queryForAllShortcuts(user);
2832 updateDeepShortcutMap(null, user, shortcuts);
2836 synchronized (LoaderTask.this) {
2840 mDeepShortcutsLoaded = true;
2843 bindDeepShortcuts();
2846 public void dumpState() {
2847 synchronized (sBgLock) {
2848 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2849 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2850 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2851 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2857 * Clear all the shortcuts for the given package, and re-add the new shortcuts.
2859 private void updateDeepShortcutMap(
2860 String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
2861 if (packageName != null) {
2862 Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
2863 while (keysIter.hasNext()) {
2864 ComponentKey next = keysIter.next();
2865 if (next.componentName.getPackageName().equals(packageName)
2866 && next.user.equals(user)) {
2872 // Now add the new shortcuts to the map.
2873 for (ShortcutInfoCompat shortcut : shortcuts) {
2874 boolean shouldShowInContainer = shortcut.isEnabled()
2875 && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
2876 if (shouldShowInContainer) {
2877 ComponentKey targetComponent
2878 = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
2879 mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
2884 public void bindDeepShortcuts() {
2885 final MultiHashMap<ComponentKey, String> shortcutMapCopy = mBgDeepShortcutMap.clone();
2886 Runnable r = new Runnable() {
2889 Callbacks callbacks = getCallback();
2890 if (callbacks != null) {
2891 callbacks.bindDeepShortcutMap(shortcutMapCopy);
2899 * Refreshes the cached shortcuts if the shortcut permission has changed.
2900 * Current implementation simply reloads the workspace, but it can be optimized to
2901 * use partial updates similar to {@link UserManagerCompat}
2903 public void refreshShortcutsIfRequired() {
2904 if (Utilities.isNycMR1OrAbove()) {
2905 sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
2906 sWorker.post(mShortcutPermissionCheckRunnable);
2911 * Called when the icons for packages have been updated in the icon cache.
2913 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2914 final Callbacks callbacks = getCallback();
2915 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2916 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2918 // If any package icon has changed (app was updated while launcher was dead),
2919 // update the corresponding shortcuts.
2920 synchronized (sBgLock) {
2921 for (ItemInfo info : sBgItemsIdMap) {
2922 if (info instanceof ShortcutInfo && user.equals(info.user)
2923 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2924 ShortcutInfo si = (ShortcutInfo) info;
2925 ComponentName cn = si.getTargetComponent();
2926 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2927 si.updateIcon(mIconCache);
2928 updatedShortcuts.add(si);
2932 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2935 bindUpdatedShortcuts(updatedShortcuts, user);
2937 if (!updatedApps.isEmpty()) {
2938 mHandler.post(new Runnable() {
2941 Callbacks cb = getCallback();
2942 if (cb != null && callbacks == cb) {
2943 cb.bindAppsUpdated(updatedApps);
2950 private void bindUpdatedShortcuts(
2951 ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
2952 bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
2955 private void bindUpdatedShortcuts(
2956 final ArrayList<ShortcutInfo> updatedShortcuts,
2957 final ArrayList<ShortcutInfo> removedShortcuts,
2958 final UserHandleCompat user) {
2959 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
2960 final Callbacks callbacks = getCallback();
2961 mHandler.post(new Runnable() {
2964 Callbacks cb = getCallback();
2965 if (cb != null && callbacks == cb) {
2966 cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
2973 void enqueueItemUpdatedTask(Runnable task) {
2977 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
2980 public void onReceive(Context context, Intent intent) {
2981 synchronized (sBgLock) {
2982 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2983 .getInstance(mApp.getContext());
2984 final PackageManager manager = context.getPackageManager();
2985 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2986 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2987 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2988 UserHandleCompat user = entry.getKey();
2989 packagesRemoved.clear();
2990 packagesUnavailable.clear();
2991 for (String pkg : entry.getValue()) {
2992 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2993 if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
2994 packagesUnavailable.add(pkg);
2996 packagesRemoved.add(pkg);
3000 if (!packagesRemoved.isEmpty()) {
3001 enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
3002 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
3004 if (!packagesUnavailable.isEmpty()) {
3005 enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
3006 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
3009 sPendingPackages.clear();
3014 private class PackageUpdatedTask implements Runnable {
3017 UserHandleCompat mUser;
3019 public static final int OP_NONE = 0;
3020 public static final int OP_ADD = 1;
3021 public static final int OP_UPDATE = 2;
3022 public static final int OP_REMOVE = 3; // uninstlled
3023 public static final int OP_UNAVAILABLE = 4; // external media unmounted
3024 public static final int OP_SUSPEND = 5; // package suspended
3025 public static final int OP_UNSUSPEND = 6; // package unsuspended
3026 public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
3028 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
3030 mPackages = packages;
3035 if (!mHasLoaderCompletedOnce) {
3036 // Loader has not yet run.
3039 final Context context = mApp.getContext();
3041 final String[] packages = mPackages;
3042 final int N = packages.length;
3043 FlagOp flagOp = FlagOp.NO_OP;
3044 StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
3047 for (int i=0; i<N; i++) {
3048 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3049 mIconCache.updateIconsForPkg(packages[i], mUser);
3050 mBgAllAppsList.addPackage(context, packages[i], mUser);
3053 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3054 if (heuristic != null) {
3055 heuristic.processPackageAdd(mPackages);
3060 for (int i=0; i<N; i++) {
3061 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3062 mIconCache.updateIconsForPkg(packages[i], mUser);
3063 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3064 mApp.getWidgetCache().removePackage(packages[i], mUser);
3066 // Since package was just updated, the target must be available now.
3067 flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
3070 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3071 if (heuristic != null) {
3072 heuristic.processPackageRemoved(mPackages);
3074 for (int i=0; i<N; i++) {
3075 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3076 mIconCache.removeIconsForPkg(packages[i], mUser);
3080 case OP_UNAVAILABLE:
3081 for (int i=0; i<N; i++) {
3082 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3083 mBgAllAppsList.removePackage(packages[i], mUser);
3084 mApp.getWidgetCache().removePackage(packages[i], mUser);
3086 flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
3090 flagOp = mOp == OP_SUSPEND ?
3091 FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
3092 FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
3093 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
3094 mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
3096 case OP_USER_AVAILABILITY_CHANGE:
3097 flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
3098 ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
3099 : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
3100 // We want to update all packages for this user.
3101 pkgFilter = StringFilter.matchesAll();
3102 mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
3106 ArrayList<AppInfo> added = null;
3107 ArrayList<AppInfo> modified = null;
3108 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3110 if (mBgAllAppsList.added.size() > 0) {
3111 added = new ArrayList<>(mBgAllAppsList.added);
3112 mBgAllAppsList.added.clear();
3114 if (mBgAllAppsList.modified.size() > 0) {
3115 modified = new ArrayList<>(mBgAllAppsList.modified);
3116 mBgAllAppsList.modified.clear();
3118 if (mBgAllAppsList.removed.size() > 0) {
3119 removedApps.addAll(mBgAllAppsList.removed);
3120 mBgAllAppsList.removed.clear();
3123 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
3125 if (added != null) {
3126 addAppsToAllApps(context, added);
3127 for (AppInfo ai : added) {
3128 addedOrUpdatedApps.put(ai.componentName, ai);
3132 if (modified != null) {
3133 final Callbacks callbacks = getCallback();
3134 final ArrayList<AppInfo> modifiedFinal = modified;
3135 for (AppInfo ai : modified) {
3136 addedOrUpdatedApps.put(ai.componentName, ai);
3139 mHandler.post(new Runnable() {
3141 Callbacks cb = getCallback();
3142 if (callbacks == cb && cb != null) {
3143 callbacks.bindAppsUpdated(modifiedFinal);
3149 // Update shortcut infos
3150 if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
3151 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3152 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3153 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3155 synchronized (sBgLock) {
3156 for (ItemInfo info : sBgItemsIdMap) {
3157 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3158 ShortcutInfo si = (ShortcutInfo) info;
3159 boolean infoUpdated = false;
3160 boolean shortcutUpdated = false;
3162 // Update shortcuts which use iconResource.
3163 if ((si.iconResource != null)
3164 && pkgFilter.matches(si.iconResource.packageName)) {
3165 Bitmap icon = Utilities.createIconBitmap(
3166 si.iconResource.packageName,
3167 si.iconResource.resourceName, context);
3170 si.usingFallbackIcon = false;
3175 ComponentName cn = si.getTargetComponent();
3176 if (cn != null && pkgFilter.matches(cn.getPackageName())) {
3177 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3179 if (si.isPromise()) {
3180 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3181 // Auto install icon
3182 PackageManager pm = context.getPackageManager();
3183 ResolveInfo matched = pm.resolveActivity(
3184 new Intent(Intent.ACTION_MAIN)
3185 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3186 PackageManager.MATCH_DEFAULT_ONLY);
3187 if (matched == null) {
3188 // Try to find the best match activity.
3189 Intent intent = pm.getLaunchIntentForPackage(
3190 cn.getPackageName());
3191 if (intent != null) {
3192 cn = intent.getComponent();
3193 appInfo = addedOrUpdatedApps.get(cn);
3196 if ((intent == null) || (appInfo == null)) {
3197 removedShortcuts.add(si);
3200 si.promisedIntent = intent;
3204 // Restore the shortcut.
3205 if (appInfo != null) {
3206 si.flags = appInfo.flags;
3209 si.intent = si.promisedIntent;
3210 si.promisedIntent = null;
3211 si.status = ShortcutInfo.DEFAULT;
3213 si.updateIcon(mIconCache);
3216 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3217 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
3218 si.updateIcon(mIconCache);
3219 si.title = Utilities.trim(appInfo.title);
3220 si.contentDescription = appInfo.contentDescription;
3224 int oldDisabledFlags = si.isDisabled;
3225 si.isDisabled = flagOp.apply(si.isDisabled);
3226 if (si.isDisabled != oldDisabledFlags) {
3227 shortcutUpdated = true;
3231 if (infoUpdated || shortcutUpdated) {
3232 updatedShortcuts.add(si);
3235 updateItemInDatabase(context, si);
3237 } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
3238 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3239 if (mUser.equals(widgetInfo.user)
3240 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
3241 && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
3242 widgetInfo.restoreStatus &=
3243 ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
3244 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
3246 // adding this flag ensures that launcher shows 'click to setup'
3247 // if the widget has a config activity. In case there is no config
3248 // activity, it will be marked as 'restored' during bind.
3249 widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3251 widgets.add(widgetInfo);
3252 updateItemInDatabase(context, widgetInfo);
3258 bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
3259 if (!removedShortcuts.isEmpty()) {
3260 deleteItemsFromDatabase(context, removedShortcuts);
3263 if (!widgets.isEmpty()) {
3264 final Callbacks callbacks = getCallback();
3265 mHandler.post(new Runnable() {
3267 Callbacks cb = getCallback();
3268 if (callbacks == cb && cb != null) {
3269 callbacks.bindWidgetsRestored(widgets);
3276 final HashSet<String> removedPackages = new HashSet<>();
3277 final HashSet<ComponentName> removedComponents = new HashSet<>();
3278 if (mOp == OP_REMOVE) {
3279 // Mark all packages in the broadcast to be removed
3280 Collections.addAll(removedPackages, packages);
3282 // No need to update the removedComponents as
3283 // removedPackages is a super-set of removedComponents
3284 } else if (mOp == OP_UPDATE) {
3285 // Mark disabled packages in the broadcast to be removed
3286 for (int i=0; i<N; i++) {
3287 if (isPackageDisabled(context, packages[i], mUser)) {
3288 removedPackages.add(packages[i]);
3292 // Update removedComponents as some components can get removed during package update
3293 for (AppInfo info : removedApps) {
3294 removedComponents.add(info.componentName);
3298 if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
3299 for (String pn : removedPackages) {
3300 deletePackageFromDatabase(context, pn, mUser);
3302 for (ComponentName cn : removedComponents) {
3303 deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
3306 // Remove any queued items from the install queue
3307 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
3309 // Call the components-removed callback
3310 final Callbacks callbacks = getCallback();
3311 mHandler.post(new Runnable() {
3313 Callbacks cb = getCallback();
3314 if (callbacks == cb && cb != null) {
3315 callbacks.bindWorkspaceComponentsRemoved(
3316 removedPackages, removedComponents, mUser);
3322 if (!removedApps.isEmpty()) {
3323 // Remove corresponding apps from All-Apps
3324 final Callbacks callbacks = getCallback();
3325 mHandler.post(new Runnable() {
3327 Callbacks cb = getCallback();
3328 if (callbacks == cb && cb != null) {
3329 callbacks.bindAppInfosRemoved(removedApps);
3335 // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
3336 // get widget update signals.
3337 if (!Utilities.ATLEAST_MARSHMALLOW &&
3338 (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
3339 final Callbacks callbacks = getCallback();
3340 mHandler.post(new Runnable() {
3342 Callbacks cb = getCallback();
3343 if (callbacks == cb && cb != null) {
3344 callbacks.notifyWidgetProvidersChanged();
3353 * Repopulates the shortcut info, possibly updating any icon already on the workspace.
3355 public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
3356 enqueueItemUpdatedTask(new Runnable() {
3359 info.updateFromDeepShortcutInfo(
3360 fullDetail, LauncherAppState.getInstance().getContext());
3361 ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
3363 bindUpdatedShortcuts(update, fullDetail.getUserHandle());
3368 private class ShortcutsChangedTask implements Runnable {
3369 private final String mPackageName;
3370 private final List<ShortcutInfoCompat> mShortcuts;
3371 private final UserHandleCompat mUser;
3372 private final boolean mUpdateIdMap;
3374 public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
3375 UserHandleCompat user, boolean updateIdMap) {
3376 mPackageName = packageName;
3377 mShortcuts = shortcuts;
3379 mUpdateIdMap = updateIdMap;
3384 mDeepShortcutManager.onShortcutsChanged(mShortcuts);
3386 // Find ShortcutInfo's that have changed on the workspace.
3387 final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
3388 MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
3389 for (ItemInfo itemInfo : sBgItemsIdMap) {
3390 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
3391 ShortcutInfo si = (ShortcutInfo) itemInfo;
3392 if (si.getPromisedIntent().getPackage().equals(mPackageName)
3393 && si.user.equals(mUser)) {
3394 idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
3399 final Context context = LauncherAppState.getInstance().getContext();
3400 final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
3401 if (!idsToWorkspaceShortcutInfos.isEmpty()) {
3402 // Update the workspace to reflect the changes to updated shortcuts residing on it.
3403 List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
3404 mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
3405 for (ShortcutInfoCompat fullDetails : shortcuts) {
3406 List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
3407 .remove(fullDetails.getId());
3408 if (!fullDetails.isPinned()) {
3409 // The shortcut was previously pinned but is no longer, so remove it from
3410 // the workspace and our pinned shortcut counts.
3411 // Note that we put this check here, after querying for full details,
3412 // because there's a possible race condition between pinning and
3413 // receiving this callback.
3414 removedShortcutInfos.addAll(shortcutInfos);
3417 for (ShortcutInfo shortcutInfo : shortcutInfos) {
3418 shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
3419 updatedShortcutInfos.add(shortcutInfo);
3424 // If there are still entries in idsToWorkspaceShortcutInfos, that means that
3425 // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
3426 // means they were cleared, so we remove and unpin them now.
3427 for (String id : idsToWorkspaceShortcutInfos.keySet()) {
3428 removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
3431 bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
3432 if (!removedShortcutInfos.isEmpty()) {
3433 deleteItemsFromDatabase(context, removedShortcutInfos);
3437 // Update the deep shortcut map if the list of ids has changed for an activity.
3438 updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
3439 bindDeepShortcuts();
3445 * Task to handle changing of lock state of the user
3447 private class UserLockStateChangedTask implements Runnable {
3449 private final UserHandleCompat mUser;
3451 public UserLockStateChangedTask(UserHandleCompat user) {
3457 boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
3458 Context context = mApp.getContext();
3460 HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
3461 if (isUserUnlocked) {
3462 List<ShortcutInfoCompat> shortcuts =
3463 mDeepShortcutManager.queryForPinnedShortcuts(null, mUser);
3464 if (mDeepShortcutManager.wasLastCallSuccess()) {
3465 for (ShortcutInfoCompat shortcut : shortcuts) {
3466 pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
3469 // Shortcut manager can fail due to some race condition when the lock state
3470 // changes too frequently. For the purpose of the update,
3471 // consider it as still locked.
3472 isUserUnlocked = false;
3476 // Update the workspace to reflect the changes to updated shortcuts residing on it.
3477 ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
3478 ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
3479 for (ItemInfo itemInfo : sBgItemsIdMap) {
3480 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
3481 && mUser.equals(itemInfo.user)) {
3482 ShortcutInfo si = (ShortcutInfo) itemInfo;
3483 if (isUserUnlocked) {
3484 ShortcutInfoCompat shortcut =
3485 pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
3486 // We couldn't verify the shortcut during loader. If its no longer available
3487 // (probably due to clear data), delete the workspace item as well
3488 if (shortcut == null) {
3489 deletedShortcutInfos.add(si);
3492 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
3493 si.updateFromDeepShortcutInfo(shortcut, context);
3495 si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
3497 updatedShortcutInfos.add(si);
3500 bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
3501 if (!deletedShortcutInfos.isEmpty()) {
3502 deleteItemsFromDatabase(context, deletedShortcutInfos);
3505 // Remove shortcut id map for that user
3506 Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
3507 while (keysIter.hasNext()) {
3508 if (keysIter.next().user.equals(mUser)) {
3513 if (isUserUnlocked) {
3514 updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
3516 bindDeepShortcuts();
3520 private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
3521 mHandler.post(new Runnable() {
3524 Callbacks cb = getCallback();
3525 if (callbacks == cb && cb != null) {
3526 callbacks.bindWidgetsModel(model);
3532 public void refreshAndBindWidgetsAndShortcuts(
3533 final Callbacks callbacks, final boolean bindFirst) {
3534 runOnWorkerThread(new Runnable() {
3537 if (bindFirst && !mBgWidgetsModel.isEmpty()) {
3538 bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
3540 final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
3541 bindWidgetsModel(callbacks, model);
3542 // update the Widget entries inside DB on the worker thread.
3543 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3544 model.getRawList());
3549 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3550 UserHandleCompat user) {
3551 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3552 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3555 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3556 UserHandleCompat user) {
3560 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3561 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3564 return launcherApps.isActivityEnabledForProfile(cn, user);
3567 public static boolean isValidPackage(Context context, String packageName,
3568 UserHandleCompat user) {
3569 if (packageName == null) {
3572 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3573 return launcherApps.isPackageEnabledForProfile(packageName, user);
3577 * Make an ShortcutInfo object for a restored application or shortcut item that points
3578 * to a package that is not yet installed on the system.
3580 public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
3581 int promiseType, int itemType, CursorIconInfo iconInfo) {
3582 final ShortcutInfo info = new ShortcutInfo();
3583 info.user = UserHandleCompat.myUserHandle();
3585 Bitmap icon = iconInfo.loadIcon(c, info);
3586 // the fallback icon
3588 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3593 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3594 String title = iconInfo.getTitle(c);
3595 if (!TextUtils.isEmpty(title)) {
3596 info.title = Utilities.trim(title);
3598 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3599 if (TextUtils.isEmpty(info.title)) {
3600 info.title = iconInfo.getTitle(c);
3603 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3606 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3607 info.itemType = itemType;
3608 info.promisedIntent = intent;
3609 info.status = promiseType;
3614 * Make an Intent object for a restored application or shortcut item that points
3615 * to the market page for the item.
3617 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3618 ComponentName componentName = intent.getComponent();
3619 return getMarketIntent(componentName.getPackageName());
3622 static Intent getMarketIntent(String packageName) {
3623 return new Intent(Intent.ACTION_VIEW)
3624 .setData(new Uri.Builder()
3626 .authority("details")
3627 .appendQueryParameter("id", packageName)
3632 * Make an ShortcutInfo object for a shortcut that is an application.
3634 * If c is not null, then it will be used to fill in missing data like the title and icon.
3636 public ShortcutInfo getAppShortcutInfo(Intent intent,
3637 UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
3638 boolean allowMissingTarget, boolean useLowResIcon) {
3640 Log.d(TAG, "Null user found in getShortcutInfo");
3644 ComponentName componentName = intent.getComponent();
3645 if (componentName == null) {
3646 Log.d(TAG, "Missing component found in getShortcutInfo");
3650 Intent newIntent = new Intent(intent.getAction(), null);
3651 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3652 newIntent.setComponent(componentName);
3653 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3654 if ((lai == null) && !allowMissingTarget) {
3655 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3659 final ShortcutInfo info = new ShortcutInfo();
3660 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3661 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3662 Bitmap icon = iconInfo.loadIcon(c);
3663 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3666 if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
3667 info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
3671 if (TextUtils.isEmpty(info.title) && c != null) {
3672 info.title = iconInfo.getTitle(c);
3675 // fall back to the class name of the activity
3676 if (info.title == null) {
3677 info.title = componentName.getClassName();
3680 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3682 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3684 info.flags = AppInfo.initFlags(lai);
3689 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3691 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3692 for (ItemInfo i : infos) {
3693 if (i instanceof ShortcutInfo) {
3694 ShortcutInfo info = (ShortcutInfo) i;
3695 ComponentName cn = info.getTargetComponent();
3696 if (cn != null && f.filterItem(null, info, cn)) {
3699 } else if (i instanceof FolderInfo) {
3700 FolderInfo info = (FolderInfo) i;
3701 for (ShortcutInfo s : info.contents) {
3702 ComponentName cn = s.getTargetComponent();
3703 if (cn != null && f.filterItem(info, s, cn)) {
3707 } else if (i instanceof LauncherAppWidgetInfo) {
3708 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3709 ComponentName cn = info.providerName;
3710 if (cn != null && f.filterItem(null, info, cn)) {
3715 return new ArrayList<ItemInfo>(filtered);
3718 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3719 final UserHandleCompat user) {
3720 ItemInfoFilter filter = new ItemInfoFilter() {
3722 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3723 if (info.user == null) {
3724 return cn.equals(cname);
3726 return cn.equals(cname) && info.user.equals(user);
3730 return filterItemInfos(sBgItemsIdMap, filter);
3734 * Make an ShortcutInfo object for a shortcut that isn't an application.
3736 @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
3737 final ShortcutInfo info = new ShortcutInfo();
3738 // Non-app shortcuts are only supported for current user.
3739 info.user = UserHandleCompat.myUserHandle();
3740 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3742 // TODO: If there's an explicit component and we can't install that, delete it.
3744 loadInfoFromCursor(info, c, iconInfo);
3749 * Make an ShortcutInfo object for a shortcut that isn't an application.
3751 public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
3752 info.title = iconInfo.getTitle(c);
3753 Bitmap icon = iconInfo.loadIcon(c, info);
3754 // the fallback icon
3756 icon = mIconCache.getDefaultIcon(info.user);
3757 info.usingFallbackIcon = true;
3762 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3763 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3764 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3765 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3767 if (intent == null) {
3768 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3769 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3774 boolean customIcon = false;
3775 ShortcutIconResource iconResource = null;
3777 if (bitmap instanceof Bitmap) {
3778 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3781 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3782 if (extra instanceof ShortcutIconResource) {
3783 iconResource = (ShortcutIconResource) extra;
3784 icon = Utilities.createIconBitmap(iconResource.packageName,
3785 iconResource.resourceName, context);
3789 final ShortcutInfo info = new ShortcutInfo();
3791 // Only support intents for current user for now. Intents sent from other
3792 // users wouldn't get here without intent forwarding anyway.
3793 info.user = UserHandleCompat.myUserHandle();
3795 icon = mIconCache.getDefaultIcon(info.user);
3796 info.usingFallbackIcon = true;
3800 info.title = Utilities.trim(name);
3801 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3802 info.intent = intent;
3803 info.iconResource = iconResource;
3809 * Return an existing FolderInfo object if we have encountered this ID previously,
3810 * or make a new one.
3812 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3813 // See if a placeholder was created for us already
3814 FolderInfo folderInfo = folders.get(id);
3815 if (folderInfo == null) {
3816 // No placeholder -- create a new instance
3817 folderInfo = new FolderInfo();
3818 folders.put(id, folderInfo);
3824 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3825 return (provider != null) && (provider.provider != null)
3826 && (provider.provider.getPackageName() != null);
3829 public void dumpState() {
3830 Log.d(TAG, "mCallbacks=" + mCallbacks);
3831 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3832 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3833 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3834 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3835 if (mLoaderTask != null) {
3836 mLoaderTask.dumpState();
3838 Log.d(TAG, "mLoaderTask=null");
3842 public Callbacks getCallback() {
3843 return mCallbacks != null ? mCallbacks.get() : null;
3847 * @return {@link FolderInfo} if its already loaded.
3849 public FolderInfo findFolderById(Long folderId) {
3850 synchronized (sBgLock) {
3851 return sBgFolders.get(folderId);
3855 @Thunk class DeferredMainThreadExecutor implements Executor {
3858 public void execute(Runnable command) {
3859 runOnMainThread(command);
3864 * @return the looper for the worker thread which can be used to start background tasks.
3866 public static Looper getWorkerLooper() {
3867 return sWorkerThread.getLooper();