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.app.SearchManager;
20 import android.appwidget.AppWidgetManager;
21 import android.appwidget.AppWidgetProviderInfo;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentResolver;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.Intent.ShortcutIconResource;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ProviderInfo;
33 import android.content.pm.ResolveInfo;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.net.Uri;
37 import android.os.Environment;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Parcelable;
42 import android.os.Process;
43 import android.os.SystemClock;
44 import android.os.TransactionTooLargeException;
45 import android.provider.BaseColumns;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.LongSparseArray;
49 import android.util.Pair;
51 import com.android.launcher3.compat.AppWidgetManagerCompat;
52 import com.android.launcher3.compat.LauncherActivityInfoCompat;
53 import com.android.launcher3.compat.LauncherAppsCompat;
54 import com.android.launcher3.compat.PackageInstallerCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.UserHandleCompat;
57 import com.android.launcher3.compat.UserManagerCompat;
58 import com.android.launcher3.model.MigrateFromRestoreTask;
59 import com.android.launcher3.model.WidgetsModel;
60 import com.android.launcher3.util.ComponentKey;
61 import com.android.launcher3.util.CursorIconInfo;
62 import com.android.launcher3.util.LongArrayMap;
63 import com.android.launcher3.util.ManagedProfileHeuristic;
64 import com.android.launcher3.util.Thunk;
66 import java.lang.ref.WeakReference;
67 import java.net.URISyntaxException;
68 import java.security.InvalidParameterException;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.Comparator;
74 import java.util.HashMap;
75 import java.util.HashSet;
76 import java.util.Iterator;
77 import java.util.List;
78 import java.util.Map.Entry;
82 * Maintains in-memory state of the Launcher. It is expected that there should be only one
83 * LauncherModel object held in a static. Also provide APIs for updating the database state
86 public class LauncherModel extends BroadcastReceiver
87 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
88 static final boolean DEBUG_LOADERS = false;
89 private static final boolean DEBUG_RECEIVER = false;
90 private static final boolean REMOVE_UNRESTORED_ICONS = true;
92 static final String TAG = "Launcher.Model";
94 public static final int LOADER_FLAG_NONE = 0;
95 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
96 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
98 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
99 private static final long INVALID_SCREEN_ID = -1L;
101 @Thunk final boolean mAppsCanBeOnRemoveableStorage;
102 private final boolean mOldContentProviderExists;
104 @Thunk final LauncherAppState mApp;
105 @Thunk final Object mLock = new Object();
106 @Thunk DeferredHandler mHandler = new DeferredHandler();
107 @Thunk LoaderTask mLoaderTask;
108 @Thunk boolean mIsLoaderTaskRunning;
109 @Thunk boolean mHasLoaderCompletedOnce;
111 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
113 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
115 sWorkerThread.start();
117 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
119 // We start off with everything not loaded. After that, we assume that
120 // our monitoring of the package manager provides all updates and we never
121 // need to do a requery. These are only ever touched from the loader thread.
122 @Thunk boolean mWorkspaceLoaded;
123 @Thunk boolean mAllAppsLoaded;
125 // When we are loading pages synchronously, we can't just post the binding of items on the side
126 // pages as this delays the rotation process. Instead, we wait for a callback from the first
127 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
128 // a normal load, we also clear this set of Runnables.
129 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
132 * Set of runnables to be called on the background thread after the workspace binding
135 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
137 @Thunk WeakReference<Callbacks> mCallbacks;
139 // < only access in worker thread >
140 AllAppsList mBgAllAppsList;
141 // Entire list of widgets.
142 WidgetsModel mBgWidgetsModel;
144 // The lock that must be acquired before referencing any static bg data structures. Unlike
145 // other locks, this one can generally be held long-term because we never expect any of these
146 // static data structures to be referenced outside of the worker thread except on the first
147 // load after configuration change.
148 static final Object sBgLock = new Object();
150 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
151 // LauncherModel to their ids
152 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
154 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
155 // created by LauncherModel that are directly on the home screen (however, no widgets or
156 // shortcuts within folders).
157 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
159 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
160 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
161 new ArrayList<LauncherAppWidgetInfo>();
163 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
164 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
166 // sBgWorkspaceScreens is the ordered set of workspace screens.
167 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
169 // sBgWidgetProviders is the set of widget providers including custom internal widgets
170 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
172 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
173 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
174 new HashMap<UserHandleCompat, HashSet<String>>();
176 // </ only access in worker thread >
178 @Thunk IconCache mIconCache;
180 @Thunk final LauncherAppsCompat mLauncherApps;
181 @Thunk final UserManagerCompat mUserManager;
183 public interface Callbacks {
184 public boolean setLoadOnResume();
185 public int getCurrentWorkspaceScreen();
186 public void startBinding();
187 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
188 boolean forceAnimateIcons);
189 public void bindScreens(ArrayList<Long> orderedScreenIds);
190 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
191 public void bindFolders(LongArrayMap<FolderInfo> folders);
192 public void finishBindingItems();
193 public void bindAppWidget(LauncherAppWidgetInfo info);
194 public void bindAllApplications(ArrayList<AppInfo> apps);
195 public void bindAppsAdded(ArrayList<Long> newScreens,
196 ArrayList<ItemInfo> addNotAnimated,
197 ArrayList<ItemInfo> addAnimated,
198 ArrayList<AppInfo> addedApps);
199 public void bindAppsUpdated(ArrayList<AppInfo> apps);
200 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
201 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
202 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
203 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
204 public void bindComponentsRemoved(ArrayList<String> packageNames,
205 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
206 public void bindAllPackages(WidgetsModel model);
207 public void bindSearchProviderChanged();
208 public boolean isAllAppsButtonRank(int rank);
209 public void onPageBoundSynchronously(int page);
210 public void dumpLogsToLocalData();
213 public interface ItemInfoFilter {
214 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
217 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
218 Context context = app.getContext();
220 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
221 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
222 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
224 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
225 ProviderInfo providerInfo =
226 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
227 ProviderInfo redirectProvider =
228 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
230 Log.d(TAG, "Old launcher provider: " + oldProvider);
231 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
233 if (mOldContentProviderExists) {
234 Log.d(TAG, "Old launcher provider exists.");
236 Log.d(TAG, "Old launcher provider does not exist.");
240 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
241 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
242 mIconCache = iconCache;
244 mLauncherApps = LauncherAppsCompat.getInstance(context);
245 mUserManager = UserManagerCompat.getInstance(context);
248 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
249 * posted on the main thread handler. */
250 @Thunk void runOnMainThread(Runnable r) {
251 if (sWorkerThread.getThreadId() == Process.myTid()) {
252 // If we are on the worker thread, post onto the main handler
259 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
260 * posted on the worker thread handler. */
261 @Thunk static void runOnWorkerThread(Runnable r) {
262 if (sWorkerThread.getThreadId() == Process.myTid()) {
265 // If we are not on the worker thread, then post to the worker handler
270 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
271 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
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 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
339 final 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 if (!updates.isEmpty()) {
362 // Push changes to the callback.
363 Runnable r = new Runnable() {
365 Callbacks callbacks = getCallback();
366 if (callbacks != null) {
367 callbacks.bindShortcutsChanged(updates,
368 new ArrayList<ShortcutInfo>(), user);
377 runOnWorkerThread(updateRunnable);
380 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
381 final Callbacks callbacks = getCallback();
383 if (allAppsApps == null) {
384 throw new RuntimeException("allAppsApps must not be null");
386 if (allAppsApps.isEmpty()) {
390 // Process the newly added applications and add them to the database first
391 Runnable r = new Runnable() {
393 runOnMainThread(new Runnable() {
395 Callbacks cb = getCallback();
396 if (callbacks == cb && cb != null) {
397 callbacks.bindAppsAdded(null, null, null, allAppsApps);
403 runOnWorkerThread(r);
406 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
407 int[] xy, int spanX, int spanY) {
408 LauncherAppState app = LauncherAppState.getInstance();
409 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
410 final int xCount = (int) profile.numColumns;
411 final int yCount = (int) profile.numRows;
412 boolean[][] occupied = new boolean[xCount][yCount];
413 if (occupiedPos != null) {
414 for (ItemInfo r : occupiedPos) {
415 int right = r.cellX + r.spanX;
416 int bottom = r.cellY + r.spanY;
417 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
418 for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
419 occupied[x][y] = true;
424 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
428 * Find a position on the screen for the given size or adds a new screen.
429 * @return screenId and the coordinates for the item.
431 @Thunk Pair<Long, int[]> findSpaceForItem(
433 ArrayList<Long> workspaceScreens,
434 ArrayList<Long> addedWorkspaceScreensFinal,
435 int spanX, int spanY) {
436 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
438 // Use sBgItemsIdMap as all the items are already loaded.
439 assertWorkspaceLoaded();
440 synchronized (sBgLock) {
441 for (ItemInfo info : sBgItemsIdMap) {
442 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
443 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
445 items = new ArrayList<>();
446 screenItems.put(info.screenId, items);
453 // Find appropriate space for the item.
455 int[] cordinates = new int[2];
456 boolean found = false;
458 int screenCount = workspaceScreens.size();
459 // First check the preferred screen.
460 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
461 if (preferredScreenIndex < screenCount) {
462 screenId = workspaceScreens.get(preferredScreenIndex);
463 found = findNextAvailableIconSpaceInScreen(
464 screenItems.get(screenId), cordinates, spanX, spanY);
468 // Search on any of the screens starting from the first screen.
469 for (int screen = 1; screen < screenCount; screen++) {
470 screenId = workspaceScreens.get(screen);
471 if (findNextAvailableIconSpaceInScreen(
472 screenItems.get(screenId), cordinates, spanX, spanY)) {
473 // We found a space for it
481 // Still no position found. Add a new screen to the end.
482 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
484 // Save the screen id for binding in the workspace
485 workspaceScreens.add(screenId);
486 addedWorkspaceScreensFinal.add(screenId);
488 // If we still can't find an empty space, then God help us all!!!
489 if (!findNextAvailableIconSpaceInScreen(
490 screenItems.get(screenId), cordinates, spanX, spanY)) {
491 throw new RuntimeException("Can't find space to add the item");
494 return Pair.create(screenId, cordinates);
498 * Adds the provided items to the workspace.
500 public void addAndBindAddedWorkspaceItems(final Context context,
501 final ArrayList<? extends ItemInfo> workspaceApps) {
502 final Callbacks callbacks = getCallback();
503 if (workspaceApps.isEmpty()) {
506 // Process the newly added applications and add them to the database first
507 Runnable r = new Runnable() {
509 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
510 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
512 // Get the list of workspace screens. We need to append to this list and
513 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
515 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
516 synchronized(sBgLock) {
517 for (ItemInfo item : workspaceApps) {
518 if (item instanceof ShortcutInfo) {
519 // Short-circuit this logic if the icon exists somewhere on the workspace
520 if (shortcutExists(context, item.getIntent(), item.user)) {
525 // Find appropriate space for the item.
526 Pair<Long, int[]> coords = findSpaceForItem(context,
527 workspaceScreens, addedWorkspaceScreensFinal,
529 long screenId = coords.first;
530 int[] cordinates = coords.second;
533 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
535 } else if (item instanceof AppInfo) {
536 itemInfo = ((AppInfo) item).makeShortcut();
538 throw new RuntimeException("Unexpected info type");
541 // Add the shortcut to the db
542 addItemToDatabase(context, itemInfo,
543 LauncherSettings.Favorites.CONTAINER_DESKTOP,
544 screenId, cordinates[0], cordinates[1]);
545 // Save the ShortcutInfo for binding in the workspace
546 addedShortcutsFinal.add(itemInfo);
550 // Update the workspace screens
551 updateWorkspaceScreenOrder(context, workspaceScreens);
553 if (!addedShortcutsFinal.isEmpty()) {
554 runOnMainThread(new Runnable() {
556 Callbacks cb = getCallback();
557 if (callbacks == cb && cb != null) {
558 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
559 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
560 if (!addedShortcutsFinal.isEmpty()) {
561 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
562 long lastScreenId = info.screenId;
563 for (ItemInfo i : addedShortcutsFinal) {
564 if (i.screenId == lastScreenId) {
567 addNotAnimated.add(i);
571 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
572 addNotAnimated, addAnimated, null);
579 runOnWorkerThread(r);
582 private void unbindItemInfosAndClearQueuedBindRunnables() {
583 if (sWorkerThread.getThreadId() == Process.myTid()) {
584 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
588 // Clear any deferred bind runnables
589 synchronized (mDeferredBindRunnables) {
590 mDeferredBindRunnables.clear();
593 // Remove any queued UI runnables
594 mHandler.cancelAll();
595 // Unbind all the workspace items
596 unbindWorkspaceItemsOnMainThread();
599 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
600 void unbindWorkspaceItemsOnMainThread() {
601 // Ensure that we don't use the same workspace items data structure on the main thread
602 // by making a copy of workspace items first.
603 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
604 synchronized (sBgLock) {
605 tmpItems.addAll(sBgWorkspaceItems);
606 tmpItems.addAll(sBgAppWidgets);
608 Runnable r = new Runnable() {
611 for (ItemInfo item : tmpItems) {
620 * Adds an item to the DB if it was not created previously, or move it to a new
621 * <container, screen, cellX, cellY>
623 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
624 long screenId, int cellX, int cellY) {
625 if (item.container == ItemInfo.NO_ID) {
627 addItemToDatabase(context, item, container, screenId, cellX, cellY);
629 // From somewhere else
630 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
634 static void checkItemInfoLocked(
635 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
636 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
637 if (modelItem != null && item != modelItem) {
638 // check all the data is consistent
639 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
640 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
641 ShortcutInfo shortcut = (ShortcutInfo) item;
642 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
643 modelShortcut.intent.filterEquals(shortcut.intent) &&
644 modelShortcut.id == shortcut.id &&
645 modelShortcut.itemType == shortcut.itemType &&
646 modelShortcut.container == shortcut.container &&
647 modelShortcut.screenId == shortcut.screenId &&
648 modelShortcut.cellX == shortcut.cellX &&
649 modelShortcut.cellY == shortcut.cellY &&
650 modelShortcut.spanX == shortcut.spanX &&
651 modelShortcut.spanY == shortcut.spanY &&
652 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
653 (modelShortcut.dropPos != null &&
654 shortcut.dropPos != null &&
655 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
656 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
657 // For all intents and purposes, this is the same object
662 // the modelItem needs to match up perfectly with item if our model is
663 // to be consistent with the database-- for now, just require
664 // modelItem == item or the equality check above
665 String msg = "item: " + ((item != null) ? item.toString() : "null") +
667 ((modelItem != null) ? modelItem.toString() : "null") +
668 "Error: ItemInfo passed to checkItemInfo doesn't match original";
669 RuntimeException e = new RuntimeException(msg);
670 if (stackTrace != null) {
671 e.setStackTrace(stackTrace);
677 static void checkItemInfo(final ItemInfo item) {
678 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
679 final long itemId = item.id;
680 Runnable r = new Runnable() {
682 synchronized (sBgLock) {
683 checkItemInfoLocked(itemId, item, stackTrace);
687 runOnWorkerThread(r);
690 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
691 final ItemInfo item, final String callingFunction) {
692 final long itemId = item.id;
693 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
694 final ContentResolver cr = context.getContentResolver();
696 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
697 Runnable r = new Runnable() {
699 cr.update(uri, values, null, null);
700 updateItemArrays(item, itemId, stackTrace);
703 runOnWorkerThread(r);
706 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
707 final ArrayList<ItemInfo> items, final String callingFunction) {
708 final ContentResolver cr = context.getContentResolver();
710 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
711 Runnable r = new Runnable() {
713 ArrayList<ContentProviderOperation> ops =
714 new ArrayList<ContentProviderOperation>();
715 int count = items.size();
716 for (int i = 0; i < count; i++) {
717 ItemInfo item = items.get(i);
718 final long itemId = item.id;
719 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
720 ContentValues values = valuesList.get(i);
722 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
723 updateItemArrays(item, itemId, stackTrace);
727 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
728 } catch (Exception e) {
733 runOnWorkerThread(r);
736 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
737 // Lock on mBgLock *after* the db operation
738 synchronized (sBgLock) {
739 checkItemInfoLocked(itemId, item, stackTrace);
741 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
742 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
743 // Item is in a folder, make sure this folder exists
744 if (!sBgFolders.containsKey(item.container)) {
745 // An items container is being set to a that of an item which is not in
746 // the list of Folders.
747 String msg = "item: " + item + " container being set to: " +
748 item.container + ", not in the list of folders";
753 // Items are added/removed from the corresponding FolderInfo elsewhere, such
754 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
755 // that are on the desktop, as appropriate
756 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
757 if (modelItem != null &&
758 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
759 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
760 switch (modelItem.itemType) {
761 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
762 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
763 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
764 if (!sBgWorkspaceItems.contains(modelItem)) {
765 sBgWorkspaceItems.add(modelItem);
772 sBgWorkspaceItems.remove(modelItem);
778 * Move an item in the DB to a new <container, screen, cellX, cellY>
780 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
781 final long screenId, final int cellX, final int cellY) {
782 item.container = container;
786 // We store hotseat items in canonical form which is this orientation invariant position
788 if (context instanceof Launcher && screenId < 0 &&
789 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
790 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
792 item.screenId = screenId;
795 final ContentValues values = new ContentValues();
796 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
797 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
798 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
799 values.put(LauncherSettings.Favorites.RANK, item.rank);
800 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
802 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
806 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
807 * cellX, cellY have already been updated on the ItemInfos.
809 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
810 final long container, final int screen) {
812 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
813 int count = items.size();
815 for (int i = 0; i < count; i++) {
816 ItemInfo item = items.get(i);
817 item.container = container;
819 // We store hotseat items in canonical form which is this orientation invariant position
821 if (context instanceof Launcher && screen < 0 &&
822 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
823 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
826 item.screenId = screen;
829 final ContentValues values = new ContentValues();
830 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
831 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
832 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
833 values.put(LauncherSettings.Favorites.RANK, item.rank);
834 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
836 contentValues.add(values);
838 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
842 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
844 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
845 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
846 item.container = container;
852 // We store hotseat items in canonical form which is this orientation invariant position
854 if (context instanceof Launcher && screenId < 0 &&
855 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
856 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
858 item.screenId = screenId;
861 final ContentValues values = new ContentValues();
862 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
863 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
864 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
865 values.put(LauncherSettings.Favorites.RANK, item.rank);
866 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
867 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
868 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
870 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
874 * Update an item to the database in a specified container.
876 public static void updateItemInDatabase(Context context, final ItemInfo item) {
877 final ContentValues values = new ContentValues();
878 item.onAddToDatabase(context, values);
879 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
882 private void assertWorkspaceLoaded() {
883 if (LauncherAppState.isDogfoodBuild()) {
884 synchronized (mLock) {
885 if (!mHasLoaderCompletedOnce ||
886 (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
887 throw new RuntimeException("Trying to add shortcut while loader is running");
894 * Returns true if the shortcuts already exists on the workspace. This must be called after
895 * the workspace has been loaded. We identify a shortcut by its intent.
897 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
898 assertWorkspaceLoaded();
899 final String intentWithPkg, intentWithoutPkg;
900 if (intent.getComponent() != null) {
901 // If component is not null, an intent with null package will produce
902 // the same result and should also be a match.
903 String packageName = intent.getComponent().getPackageName();
904 if (intent.getPackage() != null) {
905 intentWithPkg = intent.toUri(0);
906 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
908 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
909 intentWithoutPkg = intent.toUri(0);
912 intentWithPkg = intent.toUri(0);
913 intentWithoutPkg = intent.toUri(0);
916 synchronized (sBgLock) {
917 for (ItemInfo item : sBgItemsIdMap) {
918 if (item instanceof ShortcutInfo) {
919 ShortcutInfo info = (ShortcutInfo) item;
920 Intent targetIntent = info.promisedIntent == null
921 ? info.intent : info.promisedIntent;
922 if (targetIntent != null && info.user.equals(user)) {
923 String s = targetIntent.toUri(0);
924 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
935 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
937 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
938 final ContentResolver cr = context.getContentResolver();
939 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
940 "_id=? and (itemType=? or itemType=?)",
941 new String[] { String.valueOf(id),
942 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
945 if (c.moveToFirst()) {
946 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
947 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
948 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
949 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
950 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
951 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
952 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
954 FolderInfo folderInfo = null;
955 switch (c.getInt(itemTypeIndex)) {
956 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
957 folderInfo = findOrMakeFolder(folderList, id);
961 // Do not trim the folder label, as is was set by the user.
962 folderInfo.title = c.getString(titleIndex);
964 folderInfo.container = c.getInt(containerIndex);
965 folderInfo.screenId = c.getInt(screenIndex);
966 folderInfo.cellX = c.getInt(cellXIndex);
967 folderInfo.cellY = c.getInt(cellYIndex);
968 folderInfo.options = c.getInt(optionsIndex);
980 * Add an item to the database in a specified container. Sets the container, screen, cellX and
981 * cellY fields of the item. Also assigns an ID to the item.
983 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
984 final long screenId, final int cellX, final int cellY) {
985 item.container = container;
988 // We store hotseat items in canonical form which is this orientation invariant position
990 if (context instanceof Launcher && screenId < 0 &&
991 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
992 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
994 item.screenId = screenId;
997 final ContentValues values = new ContentValues();
998 final ContentResolver cr = context.getContentResolver();
999 item.onAddToDatabase(context, values);
1001 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1002 values.put(LauncherSettings.Favorites._ID, item.id);
1004 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1005 Runnable r = new Runnable() {
1007 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
1009 // Lock on mBgLock *after* the db operation
1010 synchronized (sBgLock) {
1011 checkItemInfoLocked(item.id, item, stackTrace);
1012 sBgItemsIdMap.put(item.id, item);
1013 switch (item.itemType) {
1014 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1015 sBgFolders.put(item.id, (FolderInfo) item);
1017 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1018 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1019 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1020 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1021 sBgWorkspaceItems.add(item);
1023 if (!sBgFolders.containsKey(item.container)) {
1024 // Adding an item to a folder that doesn't exist.
1025 String msg = "adding item: " + item + " to a folder that " +
1031 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1032 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1038 runOnWorkerThread(r);
1042 * Creates a new unique child id, for a given cell span across all layouts.
1044 static int getCellLayoutChildId(
1045 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1046 return (((int) container & 0xFF) << 24)
1047 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1050 private static ArrayList<ItemInfo> getItemsByPackageName(
1051 final String pn, final UserHandleCompat user) {
1052 ItemInfoFilter filter = new ItemInfoFilter() {
1054 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1055 return cn.getPackageName().equals(pn) && info.user.equals(user);
1058 return filterItemInfos(sBgItemsIdMap, filter);
1062 * Removes all the items from the database corresponding to the specified package.
1064 static void deletePackageFromDatabase(Context context, final String pn,
1065 final UserHandleCompat user) {
1066 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1070 * Removes the specified item from the database
1074 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1075 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1077 deleteItemsFromDatabase(context, items);
1081 * Removes the specified items from the database
1085 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1086 final ContentResolver cr = context.getContentResolver();
1087 Runnable r = new Runnable() {
1089 for (ItemInfo item : items) {
1090 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1091 cr.delete(uri, null, null);
1093 // Lock on mBgLock *after* the db operation
1094 synchronized (sBgLock) {
1095 switch (item.itemType) {
1096 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1097 sBgFolders.remove(item.id);
1098 for (ItemInfo info: sBgItemsIdMap) {
1099 if (info.container == item.id) {
1100 // We are deleting a folder which still contains items that
1101 // think they are contained by that folder.
1102 String msg = "deleting a folder (" + item + ") which still " +
1103 "contains items (" + info + ")";
1107 sBgWorkspaceItems.remove(item);
1109 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1110 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1111 sBgWorkspaceItems.remove(item);
1113 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1114 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1117 sBgItemsIdMap.remove(item.id);
1122 runOnWorkerThread(r);
1126 * Update the order of the workspace screens in the database. The array list contains
1127 * a list of screen ids in the order that they should appear.
1129 public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1130 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1131 final ContentResolver cr = context.getContentResolver();
1132 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1134 // Remove any negative screen ids -- these aren't persisted
1135 Iterator<Long> iter = screensCopy.iterator();
1136 while (iter.hasNext()) {
1137 long id = iter.next();
1143 Runnable r = new Runnable() {
1146 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1148 ops.add(ContentProviderOperation.newDelete(uri).build());
1149 int count = screensCopy.size();
1150 for (int i = 0; i < count; i++) {
1151 ContentValues v = new ContentValues();
1152 long screenId = screensCopy.get(i);
1153 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1154 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1155 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1159 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1160 } catch (Exception ex) {
1161 throw new RuntimeException(ex);
1164 synchronized (sBgLock) {
1165 sBgWorkspaceScreens.clear();
1166 sBgWorkspaceScreens.addAll(screensCopy);
1170 runOnWorkerThread(r);
1174 * Remove the contents of the specified folder from the database
1176 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1177 final ContentResolver cr = context.getContentResolver();
1179 Runnable r = new Runnable() {
1181 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1182 // Lock on mBgLock *after* the db operation
1183 synchronized (sBgLock) {
1184 sBgItemsIdMap.remove(info.id);
1185 sBgFolders.remove(info.id);
1186 sBgWorkspaceItems.remove(info);
1189 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1190 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1191 // Lock on mBgLock *after* the db operation
1192 synchronized (sBgLock) {
1193 for (ItemInfo childInfo : info.contents) {
1194 sBgItemsIdMap.remove(childInfo.id);
1199 runOnWorkerThread(r);
1203 * Set this as the current Launcher activity object for the loader.
1205 public void initialize(Callbacks callbacks) {
1206 synchronized (mLock) {
1207 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1208 // workspace to prevent leaking Launcher activities on orientation change.
1209 unbindItemInfosAndClearQueuedBindRunnables();
1210 mCallbacks = new WeakReference<Callbacks>(callbacks);
1215 public void onPackageChanged(String packageName, UserHandleCompat user) {
1216 int op = PackageUpdatedTask.OP_UPDATE;
1217 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1222 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1223 int op = PackageUpdatedTask.OP_REMOVE;
1224 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1229 public void onPackageAdded(String packageName, UserHandleCompat user) {
1230 int op = PackageUpdatedTask.OP_ADD;
1231 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1236 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1237 boolean replacing) {
1239 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1241 if (mAppsCanBeOnRemoveableStorage) {
1242 // Only rebind if we support removable storage. It catches the
1244 // apps on the external sd card need to be reloaded
1245 startLoaderFromBackground();
1248 // If we are replacing then just update the packages in the list
1249 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1250 packageNames, user));
1255 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1256 boolean replacing) {
1258 enqueuePackageUpdated(new PackageUpdatedTask(
1259 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1265 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1266 * ACTION_PACKAGE_CHANGED.
1269 public void onReceive(Context context, Intent intent) {
1270 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1272 final String action = intent.getAction();
1273 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1274 // If we have changed locale we need to clear out the labels in all apps/workspace.
1276 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
1277 Callbacks callbacks = getCallback();
1278 if (callbacks != null) {
1279 callbacks.bindSearchProviderChanged();
1281 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1282 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1283 UserManagerCompat.getInstance(context).enableAndResetCache();
1288 void forceReload() {
1289 resetLoadedState(true, true);
1291 // Do this here because if the launcher activity is running it will be restarted.
1292 // If it's not running startLoaderFromBackground will merely tell it that it needs
1294 startLoaderFromBackground();
1297 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1298 synchronized (mLock) {
1299 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1300 // mWorkspaceLoaded to true later
1302 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1303 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1308 * When the launcher is in the background, it's possible for it to miss paired
1309 * configuration changes. So whenever we trigger the loader from the background
1310 * tell the launcher that it needs to re-run the loader when it comes back instead
1313 public void startLoaderFromBackground() {
1314 boolean runLoader = false;
1315 Callbacks callbacks = getCallback();
1316 if (callbacks != null) {
1317 // Only actually run the loader if they're not paused.
1318 if (!callbacks.setLoadOnResume()) {
1323 startLoader(PagedView.INVALID_RESTORE_PAGE);
1328 * If there is already a loader task running, tell it to stop.
1330 private void stopLoaderLocked() {
1331 LoaderTask oldTask = mLoaderTask;
1332 if (oldTask != null) {
1333 oldTask.stopLocked();
1337 public boolean isCurrentCallbacks(Callbacks callbacks) {
1338 return (mCallbacks != null && mCallbacks.get() == callbacks);
1341 public void startLoader(int synchronousBindPage) {
1342 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1345 public void startLoader(int synchronousBindPage, int loadFlags) {
1346 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1347 InstallShortcutReceiver.enableInstallQueue();
1348 synchronized (mLock) {
1349 // Clear any deferred bind-runnables from the synchronized load process
1350 // We must do this before any loading/binding is scheduled below.
1351 synchronized (mDeferredBindRunnables) {
1352 mDeferredBindRunnables.clear();
1355 // Don't bother to start the thread if we know it's not going to do anything
1356 if (mCallbacks != null && mCallbacks.get() != null) {
1357 // If there is already one running, tell it to stop.
1359 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1360 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1361 && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
1362 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1364 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1365 sWorker.post(mLoaderTask);
1371 void bindRemainingSynchronousPages() {
1372 // Post the remaining side pages to be loaded
1373 if (!mDeferredBindRunnables.isEmpty()) {
1374 Runnable[] deferredBindRunnables = null;
1375 synchronized (mDeferredBindRunnables) {
1376 deferredBindRunnables = mDeferredBindRunnables.toArray(
1377 new Runnable[mDeferredBindRunnables.size()]);
1378 mDeferredBindRunnables.clear();
1380 for (final Runnable r : deferredBindRunnables) {
1386 public void stopLoader() {
1387 synchronized (mLock) {
1388 if (mLoaderTask != null) {
1389 mLoaderTask.stopLocked();
1395 * Loads the workspace screen ids in an ordered list.
1397 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1398 final ContentResolver contentResolver = context.getContentResolver();
1399 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1401 // Get screens ordered by rank.
1402 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1403 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1404 ArrayList<Long> screenIds = new ArrayList<Long>();
1406 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1407 while (sc.moveToNext()) {
1409 screenIds.add(sc.getLong(idIndex));
1410 } catch (Exception e) {
1411 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1412 + " - invalid screens: " + e, true);
1421 public boolean isAllAppsLoaded() {
1422 return mAllAppsLoaded;
1426 * Runnable for the thread that loads the contents of the launcher:
1431 private class LoaderTask implements Runnable {
1432 private Context mContext;
1433 @Thunk boolean mIsLoadingAndBindingWorkspace;
1434 private boolean mStopped;
1435 @Thunk boolean mLoadAndBindStepFinished;
1438 LoaderTask(Context context, int flags) {
1443 private void loadAndBindWorkspace() {
1444 mIsLoadingAndBindingWorkspace = true;
1446 // Load the workspace
1447 if (DEBUG_LOADERS) {
1448 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1451 if (!mWorkspaceLoaded) {
1453 synchronized (LoaderTask.this) {
1457 mWorkspaceLoaded = true;
1461 // Bind the workspace
1465 private void waitForIdle() {
1466 // Wait until the either we're stopped or the other threads are done.
1467 // This way we don't start loading all apps until the workspace has settled
1469 synchronized (LoaderTask.this) {
1470 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1472 mHandler.postIdle(new Runnable() {
1474 synchronized (LoaderTask.this) {
1475 mLoadAndBindStepFinished = true;
1476 if (DEBUG_LOADERS) {
1477 Log.d(TAG, "done with previous binding step");
1479 LoaderTask.this.notify();
1484 while (!mStopped && !mLoadAndBindStepFinished) {
1486 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1487 // wait no longer than 1sec at a time
1489 } catch (InterruptedException ex) {
1493 if (DEBUG_LOADERS) {
1494 Log.d(TAG, "waited "
1495 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1496 + "ms for previous step to finish binding");
1501 void runBindSynchronousPage(int synchronousBindPage) {
1502 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1503 // Ensure that we have a valid page index to load synchronously
1504 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1505 "valid page index");
1507 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1508 // Ensure that we don't try and bind a specified page when the pages have not been
1509 // loaded already (we should load everything asynchronously in that case)
1510 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1512 synchronized (mLock) {
1513 if (mIsLoaderTaskRunning) {
1514 // Ensure that we are never running the background loading at this point since
1515 // we also touch the background collections
1516 throw new RuntimeException("Error! Background loading is already running");
1520 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1521 // data structures, we can't allow any other thread to touch that data, but because
1522 // this call is synchronous, we can get away with not locking).
1524 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1525 // operations from the previous activity. We need to ensure that all queued operations
1526 // are executed before any synchronous binding work is done.
1529 // Divide the set of loaded items into those that we are binding synchronously, and
1530 // everything else that is to be bound normally (asynchronously).
1531 bindWorkspace(synchronousBindPage);
1532 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1538 synchronized (mLock) {
1542 mIsLoaderTaskRunning = true;
1544 // Optimize for end-user experience: if the Launcher is up and // running with the
1545 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1546 // workspace first (default).
1548 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1549 loadAndBindWorkspace();
1558 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1559 loadAndBindAllApps();
1562 // Clear out this reference, otherwise we end up holding it until all of the
1563 // callback runnables are done.
1566 synchronized (mLock) {
1567 // If we are still the last one to be scheduled, remove ourselves.
1568 if (mLoaderTask == this) {
1571 mIsLoaderTaskRunning = false;
1572 mHasLoaderCompletedOnce = true;
1576 public void stopLocked() {
1577 synchronized (LoaderTask.this) {
1584 * Gets the callbacks object. If we've been stopped, or if the launcher object
1585 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1586 * object that was around when the deferred message was scheduled, and if there's
1587 * a new Callbacks object around then also return null. This will save us from
1588 * calling onto it with data that will be ignored.
1590 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1591 synchronized (mLock) {
1596 if (mCallbacks == null) {
1600 final Callbacks callbacks = mCallbacks.get();
1601 if (callbacks != oldCallbacks) {
1604 if (callbacks == null) {
1605 Log.w(TAG, "no mCallbacks");
1613 // check & update map of what's occupied; used to discard overlapping/invalid items
1614 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
1615 ArrayList<Long> workspaceScreens) {
1616 LauncherAppState app = LauncherAppState.getInstance();
1617 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1618 final int countX = profile.numColumns;
1619 final int countY = profile.numRows;
1621 long containerIndex = item.screenId;
1622 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1623 // Return early if we detect that an item is under the hotseat button
1624 if (mCallbacks == null ||
1625 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1626 Log.e(TAG, "Error loading shortcut into hotseat " + item
1627 + " into position (" + item.screenId + ":" + item.cellX + ","
1628 + item.cellY + ") occupied by all apps");
1632 final ItemInfo[][] hotseatItems =
1633 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1635 if (item.screenId >= profile.numHotseatIcons) {
1636 Log.e(TAG, "Error loading shortcut " + item
1637 + " into hotseat position " + item.screenId
1638 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1643 if (hotseatItems != null) {
1644 if (hotseatItems[(int) item.screenId][0] != null) {
1645 Log.e(TAG, "Error loading shortcut into hotseat " + item
1646 + " into position (" + item.screenId + ":" + item.cellX + ","
1647 + item.cellY + ") occupied by "
1648 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1649 [(int) item.screenId][0]);
1652 hotseatItems[(int) item.screenId][0] = item;
1656 final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
1657 items[(int) item.screenId][0] = item;
1658 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1661 } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1662 if (!workspaceScreens.contains((Long) item.screenId)) {
1663 // The item has an invalid screen id.
1667 // Skip further checking if it is not the hotseat or workspace container
1671 if (!occupied.containsKey(item.screenId)) {
1672 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1673 occupied.put(item.screenId, items);
1676 final ItemInfo[][] screens = occupied.get(item.screenId);
1677 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1678 item.cellX < 0 || item.cellY < 0 ||
1679 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1680 Log.e(TAG, "Error loading shortcut " + item
1681 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1682 + item.cellX + "," + item.cellY
1683 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1687 // Check if any workspace icons overlap with each other
1688 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1689 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1690 if (screens[x][y] != null) {
1691 Log.e(TAG, "Error loading shortcut " + item
1692 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1700 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1701 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1702 screens[x][y] = item;
1709 /** Clears all the sBg data structures */
1710 private void clearSBgDataStructures() {
1711 synchronized (sBgLock) {
1712 sBgWorkspaceItems.clear();
1713 sBgAppWidgets.clear();
1715 sBgItemsIdMap.clear();
1716 sBgWorkspaceScreens.clear();
1720 private void loadWorkspace() {
1721 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1723 final Context context = mContext;
1724 final ContentResolver contentResolver = context.getContentResolver();
1725 final PackageManager manager = context.getPackageManager();
1726 final boolean isSafeMode = manager.isSafeMode();
1727 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1728 final boolean isSdCardReady = context.registerReceiver(null,
1729 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1731 LauncherAppState app = LauncherAppState.getInstance();
1732 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1733 int countX = profile.numColumns;
1734 int countY = profile.numRows;
1736 if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
1737 long migrationStartTime = System.currentTimeMillis();
1738 Log.v(TAG, "Starting workspace migration after restore");
1740 MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
1741 // Clear the flags before starting the task, so that we do not run the task
1742 // again, in case there was an uncaught error.
1743 MigrateFromRestoreTask.clearFlags(mContext);
1745 } catch (Exception e) {
1746 Log.e(TAG, "Error during grid migration", e);
1749 mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
1751 Log.v(TAG, "Workspace migration completed in "
1752 + (System.currentTimeMillis() - migrationStartTime));
1755 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1756 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1757 LauncherAppState.getLauncherProvider().deleteDatabase();
1760 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1761 // append the user's Launcher2 shortcuts
1762 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1763 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1765 // Make sure the default workspace is loaded
1766 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1767 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1770 synchronized (sBgLock) {
1771 clearSBgDataStructures();
1772 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1773 .getInstance(mContext).updateAndGetActiveSessionCache();
1774 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
1776 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1777 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1778 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1779 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1780 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1782 // +1 for the hotseat (it can be larger than the workspace)
1783 // Load workspace in reverse order to ensure that latest items are loaded first (and
1784 // before any earlier duplicates)
1785 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1788 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1789 final int intentIndex = c.getColumnIndexOrThrow
1790 (LauncherSettings.Favorites.INTENT);
1791 final int titleIndex = c.getColumnIndexOrThrow
1792 (LauncherSettings.Favorites.TITLE);
1793 final int containerIndex = c.getColumnIndexOrThrow(
1794 LauncherSettings.Favorites.CONTAINER);
1795 final int itemTypeIndex = c.getColumnIndexOrThrow(
1796 LauncherSettings.Favorites.ITEM_TYPE);
1797 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1798 LauncherSettings.Favorites.APPWIDGET_ID);
1799 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1800 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1801 final int screenIndex = c.getColumnIndexOrThrow(
1802 LauncherSettings.Favorites.SCREEN);
1803 final int cellXIndex = c.getColumnIndexOrThrow
1804 (LauncherSettings.Favorites.CELLX);
1805 final int cellYIndex = c.getColumnIndexOrThrow
1806 (LauncherSettings.Favorites.CELLY);
1807 final int spanXIndex = c.getColumnIndexOrThrow
1808 (LauncherSettings.Favorites.SPANX);
1809 final int spanYIndex = c.getColumnIndexOrThrow(
1810 LauncherSettings.Favorites.SPANY);
1811 final int rankIndex = c.getColumnIndexOrThrow(
1812 LauncherSettings.Favorites.RANK);
1813 final int restoredIndex = c.getColumnIndexOrThrow(
1814 LauncherSettings.Favorites.RESTORED);
1815 final int profileIdIndex = c.getColumnIndexOrThrow(
1816 LauncherSettings.Favorites.PROFILE_ID);
1817 final int optionsIndex = c.getColumnIndexOrThrow(
1818 LauncherSettings.Favorites.OPTIONS);
1819 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1821 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1822 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1823 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1827 String intentDescription;
1828 LauncherAppWidgetInfo appWidgetInfo;
1833 UserHandleCompat user;
1835 while (!mStopped && c.moveToNext()) {
1837 int itemType = c.getInt(itemTypeIndex);
1838 boolean restored = 0 != c.getInt(restoredIndex);
1839 boolean allowMissingTarget = false;
1840 container = c.getInt(containerIndex);
1843 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1844 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1845 id = c.getLong(idIndex);
1846 intentDescription = c.getString(intentIndex);
1847 serialNumber = c.getInt(profileIdIndex);
1848 user = allUsers.get(serialNumber);
1849 int promiseType = c.getInt(restoredIndex);
1850 int disabledState = 0;
1851 boolean itemReplaced = false;
1853 // User has been deleted remove the item.
1854 itemsToRemove.add(id);
1858 intent = Intent.parseUri(intentDescription, 0);
1859 ComponentName cn = intent.getComponent();
1860 if (cn != null && cn.getPackageName() != null) {
1861 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1862 cn.getPackageName(), user);
1863 boolean validComponent = validPkg &&
1864 launcherApps.isActivityEnabledForProfile(cn, user);
1866 if (validComponent) {
1868 // no special handling necessary for this item
1869 restoredRows.add(id);
1872 } else if (validPkg) {
1874 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1875 // We allow auto install apps to have their intent
1876 // updated after an install.
1877 intent = manager.getLaunchIntentForPackage(
1878 cn.getPackageName());
1879 if (intent != null) {
1880 ContentValues values = new ContentValues();
1881 values.put(LauncherSettings.Favorites.INTENT,
1883 updateItem(id, values);
1887 if (intent == null) {
1888 // The app is installed but the component is no
1889 // longer available.
1890 Launcher.addDumpLog(TAG,
1891 "Invalid component removed: " + cn, true);
1892 itemsToRemove.add(id);
1895 // no special handling necessary for this item
1896 restoredRows.add(id);
1899 } else if (restored) {
1900 // Package is not yet available but might be
1902 Launcher.addDumpLog(TAG,
1903 "package not yet restored: " + cn, true);
1905 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1906 // Restore has started once.
1907 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1908 // App restore has started. Update the flag
1909 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1910 ContentValues values = new ContentValues();
1911 values.put(LauncherSettings.Favorites.RESTORED,
1913 updateItem(id, values);
1914 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
1915 // This is a common app. Try to replace this.
1916 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
1917 CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
1918 if (parser.findDefaultApp()) {
1919 // Default app found. Replace it.
1920 intent = parser.parsedIntent;
1921 cn = intent.getComponent();
1922 ContentValues values = parser.parsedValues;
1923 values.put(LauncherSettings.Favorites.RESTORED, 0);
1924 updateItem(id, values);
1926 itemReplaced = true;
1928 } else if (REMOVE_UNRESTORED_ICONS) {
1929 Launcher.addDumpLog(TAG,
1930 "Unrestored package removed: " + cn, true);
1931 itemsToRemove.add(id);
1934 } else if (REMOVE_UNRESTORED_ICONS) {
1935 Launcher.addDumpLog(TAG,
1936 "Unrestored package removed: " + cn, true);
1937 itemsToRemove.add(id);
1940 } else if (launcherApps.isAppEnabled(
1941 manager, cn.getPackageName(),
1942 PackageManager.GET_UNINSTALLED_PACKAGES)) {
1943 // Package is present but not available.
1944 allowMissingTarget = true;
1945 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1946 } else if (!isSdCardReady) {
1947 // SdCard is not ready yet. Package might get available,
1948 // once it is ready.
1949 Launcher.addDumpLog(TAG, "Invalid package: " + cn
1950 + " (check again later)", true);
1951 HashSet<String> pkgs = sPendingPackages.get(user);
1953 pkgs = new HashSet<String>();
1954 sPendingPackages.put(user, pkgs);
1956 pkgs.add(cn.getPackageName());
1957 allowMissingTarget = true;
1958 // Add the icon on the workspace anyway.
1961 // Do not wait for external media load anymore.
1962 // Log the invalid package, and remove it
1963 Launcher.addDumpLog(TAG,
1964 "Invalid package removed: " + cn, true);
1965 itemsToRemove.add(id);
1968 } else if (cn == null) {
1969 // For shortcuts with no component, keep them as they are
1970 restoredRows.add(id);
1973 } catch (URISyntaxException e) {
1974 Launcher.addDumpLog(TAG,
1975 "Invalid uri: " + intentDescription, true);
1976 itemsToRemove.add(id);
1980 boolean useLowResIcon = container >= 0 &&
1981 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1984 if (user.equals(UserHandleCompat.myUserHandle())) {
1985 info = getAppShortcutInfo(manager, intent, user, context, null,
1986 cursorIconInfo.iconIndex, titleIndex,
1987 false, useLowResIcon);
1989 // Don't replace items for other profiles.
1990 itemsToRemove.add(id);
1993 } else if (restored) {
1994 if (user.equals(UserHandleCompat.myUserHandle())) {
1995 Launcher.addDumpLog(TAG,
1996 "constructing info for partially restored package",
1998 info = getRestoredItemInfo(c, titleIndex, intent,
1999 promiseType, itemType, cursorIconInfo, context);
2000 intent = getRestoredItemIntent(c, context, intent);
2002 // Don't restore items for other profiles.
2003 itemsToRemove.add(id);
2006 } else if (itemType ==
2007 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2008 info = getAppShortcutInfo(manager, intent, user, context, c,
2009 cursorIconInfo.iconIndex, titleIndex,
2010 allowMissingTarget, useLowResIcon);
2012 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
2014 // App shortcuts that used to be automatically added to Launcher
2015 // didn't always have the correct intent flags set, so do that
2017 if (intent.getAction() != null &&
2018 intent.getCategories() != null &&
2019 intent.getAction().equals(Intent.ACTION_MAIN) &&
2020 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2022 Intent.FLAG_ACTIVITY_NEW_TASK |
2023 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2029 info.intent = intent;
2030 info.container = container;
2031 info.screenId = c.getInt(screenIndex);
2032 info.cellX = c.getInt(cellXIndex);
2033 info.cellY = c.getInt(cellYIndex);
2034 info.rank = c.getInt(rankIndex);
2037 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2038 if (info.promisedIntent != null) {
2039 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2041 info.isDisabled = disabledState;
2042 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2043 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2046 // check & update map of what's occupied
2047 if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
2048 itemsToRemove.add(id);
2053 ComponentName cn = info.getTargetComponent();
2055 Integer progress = installingPkgs.get(cn.getPackageName());
2056 if (progress != null) {
2057 info.setInstallProgress(progress);
2059 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2064 switch (container) {
2065 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2066 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2067 sBgWorkspaceItems.add(info);
2070 // Item is in a user folder
2071 FolderInfo folderInfo =
2072 findOrMakeFolder(sBgFolders, container);
2073 folderInfo.add(info);
2076 sBgItemsIdMap.put(info.id, info);
2078 throw new RuntimeException("Unexpected null ShortcutInfo");
2082 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2083 id = c.getLong(idIndex);
2084 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2086 // Do not trim the folder label, as is was set by the user.
2087 folderInfo.title = c.getString(titleIndex);
2089 folderInfo.container = container;
2090 folderInfo.screenId = c.getInt(screenIndex);
2091 folderInfo.cellX = c.getInt(cellXIndex);
2092 folderInfo.cellY = c.getInt(cellYIndex);
2093 folderInfo.spanX = 1;
2094 folderInfo.spanY = 1;
2095 folderInfo.options = c.getInt(optionsIndex);
2097 // check & update map of what's occupied
2098 if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
2099 itemsToRemove.add(id);
2103 switch (container) {
2104 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2105 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2106 sBgWorkspaceItems.add(folderInfo);
2111 // no special handling required for restored folders
2112 restoredRows.add(id);
2115 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2116 sBgFolders.put(folderInfo.id, folderInfo);
2119 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2120 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2121 // Read all Launcher-specific widget details
2122 boolean customWidget = itemType ==
2123 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2125 int appWidgetId = c.getInt(appWidgetIdIndex);
2126 serialNumber = c.getLong(profileIdIndex);
2127 String savedProvider = c.getString(appWidgetProviderIndex);
2128 id = c.getLong(idIndex);
2129 user = allUsers.get(serialNumber);
2131 itemsToRemove.add(id);
2135 final ComponentName component =
2136 ComponentName.unflattenFromString(savedProvider);
2138 final int restoreStatus = c.getInt(restoredIndex);
2139 final boolean isIdValid = (restoreStatus &
2140 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2141 final boolean wasProviderReady = (restoreStatus &
2142 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2144 final LauncherAppWidgetProviderInfo provider =
2145 LauncherModel.getProviderInfo(context,
2146 ComponentName.unflattenFromString(savedProvider),
2149 final boolean isProviderReady = isValidProvider(provider);
2150 if (!isSafeMode && !customWidget &&
2151 wasProviderReady && !isProviderReady) {
2152 String log = "Deleting widget that isn't installed anymore: "
2153 + "id=" + id + " appWidgetId=" + appWidgetId;
2156 Launcher.addDumpLog(TAG, log, false);
2157 itemsToRemove.add(id);
2159 if (isProviderReady) {
2160 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2163 // The provider is available. So the widget is either
2164 // available or not available. We do not need to track
2165 // any future restore updates.
2166 int status = restoreStatus &
2167 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2168 if (!wasProviderReady) {
2169 // If provider was not previously ready, update the
2170 // status and UI flag.
2172 // Id would be valid only if the widget restore broadcast was received.
2174 status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
2176 status &= ~LauncherAppWidgetInfo
2177 .FLAG_PROVIDER_NOT_READY;
2180 appWidgetInfo.restoreStatus = status;
2182 Log.v(TAG, "Widget restore pending id=" + id
2183 + " appWidgetId=" + appWidgetId
2184 + " status =" + restoreStatus);
2185 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2187 appWidgetInfo.restoreStatus = restoreStatus;
2188 Integer installProgress = installingPkgs.get(component.getPackageName());
2190 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
2191 // Restore has started once.
2192 } else if (installProgress != null) {
2193 // App restore has started. Update the flag
2194 appWidgetInfo.restoreStatus |=
2195 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2196 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2197 Launcher.addDumpLog(TAG,
2198 "Unrestored widget removed: " + component, true);
2199 itemsToRemove.add(id);
2203 appWidgetInfo.installProgress =
2204 installProgress == null ? 0 : installProgress;
2207 appWidgetInfo.id = id;
2208 appWidgetInfo.screenId = c.getInt(screenIndex);
2209 appWidgetInfo.cellX = c.getInt(cellXIndex);
2210 appWidgetInfo.cellY = c.getInt(cellYIndex);
2211 appWidgetInfo.spanX = c.getInt(spanXIndex);
2212 appWidgetInfo.spanY = c.getInt(spanYIndex);
2213 appWidgetInfo.user = user;
2215 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2216 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2217 Log.e(TAG, "Widget found where container != " +
2218 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2219 itemsToRemove.add(id);
2223 appWidgetInfo.container = container;
2224 // check & update map of what's occupied
2225 if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
2226 itemsToRemove.add(id);
2230 if (!customWidget) {
2231 String providerName =
2232 appWidgetInfo.providerName.flattenToString();
2233 if (!providerName.equals(savedProvider) ||
2234 (appWidgetInfo.restoreStatus != restoreStatus)) {
2235 ContentValues values = new ContentValues();
2237 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2239 values.put(LauncherSettings.Favorites.RESTORED,
2240 appWidgetInfo.restoreStatus);
2241 updateItem(id, values);
2244 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2245 sBgAppWidgets.add(appWidgetInfo);
2249 } catch (Exception e) {
2250 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2259 // Break early if we've stopped loading
2261 clearSBgDataStructures();
2265 if (itemsToRemove.size() > 0) {
2266 // Remove dead items
2267 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2268 Utilities.createDbSelectionQuery(
2269 LauncherSettings.Favorites._ID, itemsToRemove), null);
2270 if (DEBUG_LOADERS) {
2271 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2272 LauncherSettings.Favorites._ID, itemsToRemove));
2275 // Remove any empty folder
2276 for (long folderId : LauncherAppState.getLauncherProvider()
2277 .deleteEmptyFolders()) {
2278 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2279 sBgFolders.remove(folderId);
2280 sBgItemsIdMap.remove(folderId);
2284 // Sort all the folder items and make sure the first 3 items are high resolution.
2285 for (FolderInfo folder : sBgFolders) {
2286 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
2288 for (ShortcutInfo info : folder.contents) {
2289 if (info.usingLowResIcon) {
2290 info.updateIcon(mIconCache, false);
2293 if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
2299 if (restoredRows.size() > 0) {
2300 // Update restored items that no longer require special handling
2301 ContentValues values = new ContentValues();
2302 values.put(LauncherSettings.Favorites.RESTORED, 0);
2303 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2304 Utilities.createDbSelectionQuery(
2305 LauncherSettings.Favorites._ID, restoredRows), null);
2308 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2309 context.registerReceiver(new AppsAvailabilityCheck(),
2310 new IntentFilter(StartupReceiver.SYSTEM_READY),
2314 // Remove any empty screens
2315 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2316 for (ItemInfo item: sBgItemsIdMap) {
2317 long screenId = item.screenId;
2318 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2319 unusedScreens.contains(screenId)) {
2320 unusedScreens.remove(screenId);
2324 // If there are any empty screens remove them, and update.
2325 if (unusedScreens.size() != 0) {
2326 sBgWorkspaceScreens.removeAll(unusedScreens);
2327 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2330 if (DEBUG_LOADERS) {
2331 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2332 Log.d(TAG, "workspace layout: ");
2333 int nScreens = occupied.size();
2334 for (int y = 0; y < countY; y++) {
2337 for (int i = 0; i < nScreens; i++) {
2338 long screenId = occupied.keyAt(i);
2342 ItemInfo[][] screen = occupied.valueAt(i);
2343 for (int x = 0; x < countX; x++) {
2344 if (x < screen.length && y < screen[x].length) {
2345 line += (screen[x][y] != null) ? "#" : ".";
2351 Log.d(TAG, "[ " + line + " ]");
2358 * Partially updates the item without any notification. Must be called on the worker thread.
2360 private void updateItem(long itemId, ContentValues update) {
2361 mContext.getContentResolver().update(
2362 LauncherSettings.Favorites.CONTENT_URI,
2364 BaseColumns._ID + "= ?",
2365 new String[]{Long.toString(itemId)});
2368 /** Filters the set of items who are directly or indirectly (via another container) on the
2369 * specified screen. */
2370 private void filterCurrentWorkspaceItems(long currentScreenId,
2371 ArrayList<ItemInfo> allWorkspaceItems,
2372 ArrayList<ItemInfo> currentScreenItems,
2373 ArrayList<ItemInfo> otherScreenItems) {
2374 // Purge any null ItemInfos
2375 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2376 while (iter.hasNext()) {
2377 ItemInfo i = iter.next();
2383 // Order the set of items by their containers first, this allows use to walk through the
2384 // list sequentially, build up a list of containers that are in the specified screen,
2385 // as well as all items in those containers.
2386 Set<Long> itemsOnScreen = new HashSet<Long>();
2387 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2389 public int compare(ItemInfo lhs, ItemInfo rhs) {
2390 return (int) (lhs.container - rhs.container);
2393 for (ItemInfo info : allWorkspaceItems) {
2394 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2395 if (info.screenId == currentScreenId) {
2396 currentScreenItems.add(info);
2397 itemsOnScreen.add(info.id);
2399 otherScreenItems.add(info);
2401 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2402 currentScreenItems.add(info);
2403 itemsOnScreen.add(info.id);
2405 if (itemsOnScreen.contains(info.container)) {
2406 currentScreenItems.add(info);
2407 itemsOnScreen.add(info.id);
2409 otherScreenItems.add(info);
2415 /** Filters the set of widgets which are on the specified screen. */
2416 private void filterCurrentAppWidgets(long currentScreenId,
2417 ArrayList<LauncherAppWidgetInfo> appWidgets,
2418 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2419 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2421 for (LauncherAppWidgetInfo widget : appWidgets) {
2422 if (widget == null) continue;
2423 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2424 widget.screenId == currentScreenId) {
2425 currentScreenWidgets.add(widget);
2427 otherScreenWidgets.add(widget);
2432 /** Filters the set of folders which are on the specified screen. */
2433 private void filterCurrentFolders(long currentScreenId,
2434 LongArrayMap<ItemInfo> itemsIdMap,
2435 LongArrayMap<FolderInfo> folders,
2436 LongArrayMap<FolderInfo> currentScreenFolders,
2437 LongArrayMap<FolderInfo> otherScreenFolders) {
2439 int total = folders.size();
2440 for (int i = 0; i < total; i++) {
2441 long id = folders.keyAt(i);
2442 FolderInfo folder = folders.valueAt(i);
2444 ItemInfo info = itemsIdMap.get(id);
2445 if (info == null || folder == null) continue;
2446 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2447 info.screenId == currentScreenId) {
2448 currentScreenFolders.put(id, folder);
2450 otherScreenFolders.put(id, folder);
2455 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2457 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2458 final LauncherAppState app = LauncherAppState.getInstance();
2459 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2461 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2463 public int compare(ItemInfo lhs, ItemInfo rhs) {
2464 int cellCountX = (int) profile.numColumns;
2465 int cellCountY = (int) profile.numRows;
2466 int screenOffset = cellCountX * cellCountY;
2467 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2468 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2469 lhs.cellY * cellCountX + lhs.cellX);
2470 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2471 rhs.cellY * cellCountX + rhs.cellX);
2472 return (int) (lr - rr);
2477 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2478 final ArrayList<Long> orderedScreens) {
2479 final Runnable r = new Runnable() {
2482 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2483 if (callbacks != null) {
2484 callbacks.bindScreens(orderedScreens);
2491 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2492 final ArrayList<ItemInfo> workspaceItems,
2493 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2494 final LongArrayMap<FolderInfo> folders,
2495 ArrayList<Runnable> deferredBindRunnables) {
2497 final boolean postOnMainThread = (deferredBindRunnables != null);
2499 // Bind the workspace items
2500 int N = workspaceItems.size();
2501 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2502 final int start = i;
2503 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2504 final Runnable r = new Runnable() {
2507 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2508 if (callbacks != null) {
2509 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2514 if (postOnMainThread) {
2515 synchronized (deferredBindRunnables) {
2516 deferredBindRunnables.add(r);
2524 if (!folders.isEmpty()) {
2525 final Runnable r = new Runnable() {
2527 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2528 if (callbacks != null) {
2529 callbacks.bindFolders(folders);
2533 if (postOnMainThread) {
2534 synchronized (deferredBindRunnables) {
2535 deferredBindRunnables.add(r);
2542 // Bind the widgets, one at a time
2543 N = appWidgets.size();
2544 for (int i = 0; i < N; i++) {
2545 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2546 final Runnable r = new Runnable() {
2548 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2549 if (callbacks != null) {
2550 callbacks.bindAppWidget(widget);
2554 if (postOnMainThread) {
2555 deferredBindRunnables.add(r);
2563 * Binds all loaded data to actual views on the main thread.
2565 private void bindWorkspace(int synchronizeBindPage) {
2566 final long t = SystemClock.uptimeMillis();
2569 // Don't use these two variables in any of the callback runnables.
2570 // Otherwise we hold a reference to them.
2571 final Callbacks oldCallbacks = mCallbacks.get();
2572 if (oldCallbacks == null) {
2573 // This launcher has exited and nobody bothered to tell us. Just bail.
2574 Log.w(TAG, "LoaderTask running with no launcher");
2578 // Save a copy of all the bg-thread collections
2579 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2580 ArrayList<LauncherAppWidgetInfo> appWidgets =
2581 new ArrayList<LauncherAppWidgetInfo>();
2582 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2584 final LongArrayMap<FolderInfo> folders;
2585 final LongArrayMap<ItemInfo> itemsIdMap;
2587 synchronized (sBgLock) {
2588 workspaceItems.addAll(sBgWorkspaceItems);
2589 appWidgets.addAll(sBgAppWidgets);
2590 orderedScreenIds.addAll(sBgWorkspaceScreens);
2592 folders = sBgFolders.clone();
2593 itemsIdMap = sBgItemsIdMap.clone();
2596 final boolean isLoadingSynchronously =
2597 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2598 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2599 oldCallbacks.getCurrentWorkspaceScreen();
2600 if (currScreen >= orderedScreenIds.size()) {
2601 // There may be no workspace screens (just hotseat items and an empty page).
2602 currScreen = PagedView.INVALID_RESTORE_PAGE;
2604 final int currentScreen = currScreen;
2605 final long currentScreenId = currentScreen < 0
2606 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2608 // Load all the items that are on the current page first (and in the process, unbind
2609 // all the existing workspace items before we call startBinding() below.
2610 unbindWorkspaceItemsOnMainThread();
2612 // Separate the items that are on the current screen, and all the other remaining items
2613 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2614 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2615 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2616 new ArrayList<LauncherAppWidgetInfo>();
2617 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2618 new ArrayList<LauncherAppWidgetInfo>();
2619 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2620 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2622 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2623 otherWorkspaceItems);
2624 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2626 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2628 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2629 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2631 // Tell the workspace that we're about to start binding items
2632 r = new Runnable() {
2634 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2635 if (callbacks != null) {
2636 callbacks.startBinding();
2642 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2644 // Load items on the current page
2645 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2646 currentFolders, null);
2647 if (isLoadingSynchronously) {
2648 r = new Runnable() {
2650 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2651 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2652 callbacks.onPageBoundSynchronously(currentScreen);
2659 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2660 // work until after the first render)
2661 synchronized (mDeferredBindRunnables) {
2662 mDeferredBindRunnables.clear();
2664 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2665 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2667 // Tell the workspace that we're done binding items
2668 r = new Runnable() {
2670 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2671 if (callbacks != null) {
2672 callbacks.finishBindingItems();
2675 mIsLoadingAndBindingWorkspace = false;
2677 // Run all the bind complete runnables after workspace is bound.
2678 if (!mBindCompleteRunnables.isEmpty()) {
2679 synchronized (mBindCompleteRunnables) {
2680 for (final Runnable r : mBindCompleteRunnables) {
2681 runOnWorkerThread(r);
2683 mBindCompleteRunnables.clear();
2687 // If we're profiling, ensure this is the last thing in the queue.
2688 if (DEBUG_LOADERS) {
2689 Log.d(TAG, "bound workspace in "
2690 + (SystemClock.uptimeMillis()-t) + "ms");
2695 if (isLoadingSynchronously) {
2696 synchronized (mDeferredBindRunnables) {
2697 mDeferredBindRunnables.add(r);
2704 private void loadAndBindAllApps() {
2705 if (DEBUG_LOADERS) {
2706 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2708 if (!mAllAppsLoaded) {
2710 synchronized (LoaderTask.this) {
2716 synchronized (LoaderTask.this) {
2720 mAllAppsLoaded = true;
2727 private void updateIconCache() {
2728 // Ignore packages which have a promise icon.
2729 HashSet<String> packagesToIgnore = new HashSet<>();
2730 synchronized (sBgLock) {
2731 for (ItemInfo info : sBgItemsIdMap) {
2732 if (info instanceof ShortcutInfo) {
2733 ShortcutInfo si = (ShortcutInfo) info;
2734 if (si.isPromise() && si.getTargetComponent() != null) {
2735 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2737 } else if (info instanceof LauncherAppWidgetInfo) {
2738 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2739 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2740 packagesToIgnore.add(lawi.providerName.getPackageName());
2745 mIconCache.updateDbIcons(packagesToIgnore);
2748 private void onlyBindAllApps() {
2749 final Callbacks oldCallbacks = mCallbacks.get();
2750 if (oldCallbacks == null) {
2751 // This launcher has exited and nobody bothered to tell us. Just bail.
2752 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2757 @SuppressWarnings("unchecked")
2758 final ArrayList<AppInfo> list
2759 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2760 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2761 Runnable r = new Runnable() {
2763 final long t = SystemClock.uptimeMillis();
2764 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2765 if (callbacks != null) {
2766 callbacks.bindAllApplications(list);
2767 callbacks.bindAllPackages(widgetList);
2769 if (DEBUG_LOADERS) {
2770 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2771 + (SystemClock.uptimeMillis()-t) + "ms");
2775 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2776 if (isRunningOnMainThread) {
2783 private void loadAllApps() {
2784 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2786 final Callbacks oldCallbacks = mCallbacks.get();
2787 if (oldCallbacks == null) {
2788 // This launcher has exited and nobody bothered to tell us. Just bail.
2789 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2793 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2795 // Clear the list of apps
2796 mBgAllAppsList.clear();
2797 for (UserHandleCompat user : profiles) {
2798 // Query for the set of apps
2799 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2800 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2801 if (DEBUG_LOADERS) {
2802 Log.d(TAG, "getActivityList took "
2803 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2804 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2806 // Fail if we don't have any apps
2807 // TODO: Fix this. Only fail for the current user.
2808 if (apps == null || apps.isEmpty()) {
2812 // Create the ApplicationInfos
2813 for (int i = 0; i < apps.size(); i++) {
2814 LauncherActivityInfoCompat app = apps.get(i);
2815 // This builds the icon bitmaps.
2816 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2819 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2820 if (heuristic != null) {
2821 final Runnable r = new Runnable() {
2825 heuristic.processUserApps(apps);
2828 runOnMainThread(new Runnable() {
2832 // Check isLoadingWorkspace on the UI thread, as it is updated on
2834 if (mIsLoadingAndBindingWorkspace) {
2835 synchronized (mBindCompleteRunnables) {
2836 mBindCompleteRunnables.add(r);
2839 runOnWorkerThread(r);
2845 // Huh? Shouldn't this be inside the Runnable below?
2846 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2847 mBgAllAppsList.added = new ArrayList<AppInfo>();
2849 // Post callback on main thread
2850 mHandler.post(new Runnable() {
2853 final long bindTime = SystemClock.uptimeMillis();
2854 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2855 if (callbacks != null) {
2856 callbacks.bindAllApplications(added);
2857 if (DEBUG_LOADERS) {
2858 Log.d(TAG, "bound " + added.size() + " apps in "
2859 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2862 Log.i(TAG, "not binding apps: no Launcher activity");
2866 // Cleanup any data stored for a deleted user.
2867 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2869 loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
2870 if (DEBUG_LOADERS) {
2871 Log.d(TAG, "Icons processed in "
2872 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2876 public void dumpState() {
2877 synchronized (sBgLock) {
2878 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2879 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2880 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2881 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2887 * Called when the icons for packages have been updated in the icon cache.
2889 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2890 final Callbacks callbacks = getCallback();
2891 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2892 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2894 // If any package icon has changed (app was updated while launcher was dead),
2895 // update the corresponding shortcuts.
2896 synchronized (sBgLock) {
2897 for (ItemInfo info : sBgItemsIdMap) {
2898 if (info instanceof ShortcutInfo && user.equals(info.user)
2899 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2900 ShortcutInfo si = (ShortcutInfo) info;
2901 ComponentName cn = si.getTargetComponent();
2902 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2903 si.updateIcon(mIconCache);
2904 updatedShortcuts.add(si);
2908 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2911 if (!updatedShortcuts.isEmpty()) {
2912 final UserHandleCompat userFinal = user;
2913 mHandler.post(new Runnable() {
2916 Callbacks cb = getCallback();
2917 if (cb != null && callbacks == cb) {
2918 cb.bindShortcutsChanged(updatedShortcuts,
2919 new ArrayList<ShortcutInfo>(), userFinal);
2925 if (!updatedApps.isEmpty()) {
2926 mHandler.post(new Runnable() {
2929 Callbacks cb = getCallback();
2930 if (cb != null && callbacks == cb) {
2931 cb.bindAppsUpdated(updatedApps);
2937 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2938 loadAndBindWidgetsAndShortcuts(callbacks, false);
2941 void enqueuePackageUpdated(PackageUpdatedTask task) {
2945 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
2948 public void onReceive(Context context, Intent intent) {
2949 synchronized (sBgLock) {
2950 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2951 .getInstance(mApp.getContext());
2952 final PackageManager manager = context.getPackageManager();
2953 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2954 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2955 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2956 UserHandleCompat user = entry.getKey();
2957 packagesRemoved.clear();
2958 packagesUnavailable.clear();
2959 for (String pkg : entry.getValue()) {
2960 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2961 boolean packageOnSdcard = launcherApps.isAppEnabled(
2962 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2963 if (packageOnSdcard) {
2964 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2965 packagesUnavailable.add(pkg);
2967 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2968 packagesRemoved.add(pkg);
2972 if (!packagesRemoved.isEmpty()) {
2973 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2974 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2976 if (!packagesUnavailable.isEmpty()) {
2977 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2978 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
2981 sPendingPackages.clear();
2986 private class PackageUpdatedTask implements Runnable {
2989 UserHandleCompat mUser;
2991 public static final int OP_NONE = 0;
2992 public static final int OP_ADD = 1;
2993 public static final int OP_UPDATE = 2;
2994 public static final int OP_REMOVE = 3; // uninstlled
2995 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2998 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
3000 mPackages = packages;
3005 if (!mHasLoaderCompletedOnce) {
3006 // Loader has not yet run.
3009 final Context context = mApp.getContext();
3011 final String[] packages = mPackages;
3012 final int N = packages.length;
3015 for (int i=0; i<N; i++) {
3016 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3017 mIconCache.updateIconsForPkg(packages[i], mUser);
3018 mBgAllAppsList.addPackage(context, packages[i], mUser);
3021 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3022 if (heuristic != null) {
3023 heuristic.processPackageAdd(mPackages);
3028 for (int i=0; i<N; i++) {
3029 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3030 mIconCache.updateIconsForPkg(packages[i], mUser);
3031 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3032 mApp.getWidgetCache().removePackage(packages[i], mUser);
3036 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3037 if (heuristic != null) {
3038 heuristic.processPackageRemoved(mPackages);
3040 for (int i=0; i<N; i++) {
3041 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3042 mIconCache.removeIconsForPkg(packages[i], mUser);
3046 case OP_UNAVAILABLE:
3047 for (int i=0; i<N; i++) {
3048 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3049 mBgAllAppsList.removePackage(packages[i], mUser);
3050 mApp.getWidgetCache().removePackage(packages[i], mUser);
3055 ArrayList<AppInfo> added = null;
3056 ArrayList<AppInfo> modified = null;
3057 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3059 if (mBgAllAppsList.added.size() > 0) {
3060 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3061 mBgAllAppsList.added.clear();
3063 if (mBgAllAppsList.modified.size() > 0) {
3064 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3065 mBgAllAppsList.modified.clear();
3067 if (mBgAllAppsList.removed.size() > 0) {
3068 removedApps.addAll(mBgAllAppsList.removed);
3069 mBgAllAppsList.removed.clear();
3072 final Callbacks callbacks = getCallback();
3073 if (callbacks == null) {
3074 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3078 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3079 new HashMap<ComponentName, AppInfo>();
3081 if (added != null) {
3082 addAppsToAllApps(context, added);
3083 for (AppInfo ai : added) {
3084 addedOrUpdatedApps.put(ai.componentName, ai);
3088 if (modified != null) {
3089 final ArrayList<AppInfo> modifiedFinal = modified;
3090 for (AppInfo ai : modified) {
3091 addedOrUpdatedApps.put(ai.componentName, ai);
3094 mHandler.post(new Runnable() {
3096 Callbacks cb = getCallback();
3097 if (callbacks == cb && cb != null) {
3098 callbacks.bindAppsUpdated(modifiedFinal);
3104 // Update shortcut infos
3105 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3106 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3107 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3108 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3110 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3111 synchronized (sBgLock) {
3112 for (ItemInfo info : sBgItemsIdMap) {
3113 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3114 ShortcutInfo si = (ShortcutInfo) info;
3115 boolean infoUpdated = false;
3116 boolean shortcutUpdated = false;
3118 // Update shortcuts which use iconResource.
3119 if ((si.iconResource != null)
3120 && packageSet.contains(si.iconResource.packageName)) {
3121 Bitmap icon = Utilities.createIconBitmap(
3122 si.iconResource.packageName,
3123 si.iconResource.resourceName, context);
3126 si.usingFallbackIcon = false;
3131 ComponentName cn = si.getTargetComponent();
3132 if (cn != null && packageSet.contains(cn.getPackageName())) {
3133 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3135 if (si.isPromise()) {
3136 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3137 // Auto install icon
3138 PackageManager pm = context.getPackageManager();
3139 ResolveInfo matched = pm.resolveActivity(
3140 new Intent(Intent.ACTION_MAIN)
3141 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3142 PackageManager.MATCH_DEFAULT_ONLY);
3143 if (matched == null) {
3144 // Try to find the best match activity.
3145 Intent intent = pm.getLaunchIntentForPackage(
3146 cn.getPackageName());
3147 if (intent != null) {
3148 cn = intent.getComponent();
3149 appInfo = addedOrUpdatedApps.get(cn);
3152 if ((intent == null) || (appInfo == null)) {
3153 removedShortcuts.add(si);
3156 si.promisedIntent = intent;
3160 // Restore the shortcut.
3161 if (appInfo != null) {
3162 si.flags = appInfo.flags;
3165 si.intent = si.promisedIntent;
3166 si.promisedIntent = null;
3167 si.status = ShortcutInfo.DEFAULT;
3169 si.updateIcon(mIconCache);
3172 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3173 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
3174 si.updateIcon(mIconCache);
3175 si.title = Utilities.trim(appInfo.title);
3176 si.contentDescription = appInfo.contentDescription;
3180 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3181 // Since package was just updated, the target must be available now.
3182 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3183 shortcutUpdated = true;
3187 if (infoUpdated || shortcutUpdated) {
3188 updatedShortcuts.add(si);
3191 updateItemInDatabase(context, si);
3193 } else if (info instanceof LauncherAppWidgetInfo) {
3194 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3195 if (mUser.equals(widgetInfo.user)
3196 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
3197 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3198 widgetInfo.restoreStatus &=
3199 ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
3200 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
3202 // adding this flag ensures that launcher shows 'click to setup'
3203 // if the widget has a config activity. In case there is no config
3204 // activity, it will be marked as 'restored' during bind.
3205 widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3207 widgets.add(widgetInfo);
3208 updateItemInDatabase(context, widgetInfo);
3214 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3215 mHandler.post(new Runnable() {
3218 Callbacks cb = getCallback();
3219 if (callbacks == cb && cb != null) {
3220 callbacks.bindShortcutsChanged(
3221 updatedShortcuts, removedShortcuts, mUser);
3225 if (!removedShortcuts.isEmpty()) {
3226 deleteItemsFromDatabase(context, removedShortcuts);
3229 if (!widgets.isEmpty()) {
3230 mHandler.post(new Runnable() {
3232 Callbacks cb = getCallback();
3233 if (callbacks == cb && cb != null) {
3234 callbacks.bindWidgetsRestored(widgets);
3241 final ArrayList<String> removedPackageNames =
3242 new ArrayList<String>();
3243 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3244 // Mark all packages in the broadcast to be removed
3245 removedPackageNames.addAll(Arrays.asList(packages));
3246 } else if (mOp == OP_UPDATE) {
3247 // Mark disabled packages in the broadcast to be removed
3248 for (int i=0; i<N; i++) {
3249 if (isPackageDisabled(context, packages[i], mUser)) {
3250 removedPackageNames.add(packages[i]);
3255 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3256 final int removeReason;
3257 if (mOp == OP_UNAVAILABLE) {
3258 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3260 // Remove all the components associated with this package
3261 for (String pn : removedPackageNames) {
3262 deletePackageFromDatabase(context, pn, mUser);
3264 // Remove all the specific components
3265 for (AppInfo a : removedApps) {
3266 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3267 deleteItemsFromDatabase(context, infos);
3272 // Remove any queued items from the install queue
3273 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3274 // Call the components-removed callback
3275 mHandler.post(new Runnable() {
3277 Callbacks cb = getCallback();
3278 if (callbacks == cb && cb != null) {
3279 callbacks.bindComponentsRemoved(
3280 removedPackageNames, removedApps, mUser, removeReason);
3287 if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
3288 // Always refresh for a package event on secondary user
3289 boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
3291 // Refresh widget list, if the package already had a widget.
3292 synchronized (sBgLock) {
3293 if (sBgWidgetProviders != null) {
3294 HashSet<String> pkgSet = new HashSet<>();
3295 Collections.addAll(pkgSet, mPackages);
3297 for (ComponentKey key : sBgWidgetProviders.keySet()) {
3298 needToRefresh |= key.user.equals(mUser) &&
3299 pkgSet.contains(key.componentName.getPackageName());
3304 if (!needToRefresh && mOp != OP_REMOVE) {
3305 // Refresh widget list, if there is any newly added widget
3306 PackageManager pm = context.getPackageManager();
3307 for (String pkg : mPackages) {
3308 needToRefresh |= !pm.queryBroadcastReceivers(
3309 new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
3310 .setPackage(pkg), 0).isEmpty();
3314 loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
3317 // Write all the logs to disk
3318 mHandler.post(new Runnable() {
3320 Callbacks cb = getCallback();
3321 if (callbacks == cb && cb != null) {
3322 callbacks.dumpLogsToLocalData();
3329 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
3331 ArrayList<LauncherAppWidgetProviderInfo> results =
3332 new ArrayList<LauncherAppWidgetProviderInfo>();
3334 synchronized (sBgLock) {
3335 if (sBgWidgetProviders == null || refresh) {
3336 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
3338 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
3339 LauncherAppWidgetProviderInfo info;
3341 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
3342 for (AppWidgetProviderInfo pInfo : widgets) {
3343 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3344 UserHandleCompat user = wm.getUser(info);
3345 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3348 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3349 for (CustomAppWidget widget : customWidgets) {
3350 info = new LauncherAppWidgetProviderInfo(context, widget);
3351 UserHandleCompat user = wm.getUser(info);
3352 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3354 // Replace the global list at the very end, so that if there is an exception,
3355 // previously loaded provider list is used.
3356 sBgWidgetProviders = tmpWidgetProviders;
3358 results.addAll(sBgWidgetProviders.values());
3361 } catch (Exception e) {
3362 if (e.getCause() instanceof TransactionTooLargeException) {
3363 // the returned value may be incomplete and will not be refreshed until the next
3364 // time Launcher starts.
3365 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3366 // onResume is called to refresh the widget provider list.
3367 synchronized (sBgLock) {
3368 if (sBgWidgetProviders != null) {
3369 results.addAll(sBgWidgetProviders.values());
3379 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3380 UserHandleCompat user) {
3381 synchronized (sBgLock) {
3382 if (sBgWidgetProviders == null) {
3383 getWidgetProviders(ctx, false /* refresh */);
3385 return sBgWidgetProviders.get(new ComponentKey(name, user));
3389 public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
3391 runOnWorkerThread(new Runnable() {
3394 updateWidgetsModel(refresh);
3395 final WidgetsModel model = mBgWidgetsModel.clone();
3397 mHandler.post(new Runnable() {
3400 Callbacks cb = getCallback();
3401 if (callbacks == cb && cb != null) {
3402 callbacks.bindAllPackages(model);
3406 // update the Widget entries inside DB on the worker thread.
3407 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3408 model.getRawList());
3414 * Returns a list of ResolveInfos/AppWidgetInfos.
3416 * @see #loadAndBindWidgetsAndShortcuts
3418 @Thunk void updateWidgetsModel(boolean refresh) {
3419 PackageManager packageManager = mApp.getContext().getPackageManager();
3420 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3421 widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
3422 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3423 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3424 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3427 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3428 UserHandleCompat user) {
3429 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3430 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3433 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3434 UserHandleCompat user) {
3438 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3439 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3442 return launcherApps.isActivityEnabledForProfile(cn, user);
3445 public static boolean isValidPackage(Context context, String packageName,
3446 UserHandleCompat user) {
3447 if (packageName == null) {
3450 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3451 return launcherApps.isPackageEnabledForProfile(packageName, user);
3455 * Make an ShortcutInfo object for a restored application or shortcut item that points
3456 * to a package that is not yet installed on the system.
3458 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
3459 int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
3460 final ShortcutInfo info = new ShortcutInfo();
3461 info.user = UserHandleCompat.myUserHandle();
3463 Bitmap icon = iconInfo.loadIcon(c, info, context);
3464 // the fallback icon
3466 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3471 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3472 String title = (c != null) ? c.getString(titleIndex) : null;
3473 if (!TextUtils.isEmpty(title)) {
3474 info.title = Utilities.trim(title);
3476 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3477 if (TextUtils.isEmpty(info.title)) {
3478 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3481 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3484 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3485 info.itemType = itemType;
3486 info.promisedIntent = intent;
3487 info.status = promiseType;
3492 * Make an Intent object for a restored application or shortcut item that points
3493 * to the market page for the item.
3495 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3496 ComponentName componentName = intent.getComponent();
3497 return getMarketIntent(componentName.getPackageName());
3500 static Intent getMarketIntent(String packageName) {
3501 return new Intent(Intent.ACTION_VIEW)
3502 .setData(new Uri.Builder()
3504 .authority("details")
3505 .appendQueryParameter("id", packageName)
3510 * Make an ShortcutInfo object for a shortcut that is an application.
3512 * If c is not null, then it will be used to fill in missing data like the title and icon.
3514 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3515 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3516 boolean allowMissingTarget, boolean useLowResIcon) {
3518 Log.d(TAG, "Null user found in getShortcutInfo");
3522 ComponentName componentName = intent.getComponent();
3523 if (componentName == null) {
3524 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3528 Intent newIntent = new Intent(intent.getAction(), null);
3529 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3530 newIntent.setComponent(componentName);
3531 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3532 if ((lai == null) && !allowMissingTarget) {
3533 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3537 final ShortcutInfo info = new ShortcutInfo();
3538 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3539 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3540 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3541 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3545 if (TextUtils.isEmpty(info.title) && c != null) {
3546 info.title = Utilities.trim(c.getString(titleIndex));
3549 // fall back to the class name of the activity
3550 if (info.title == null) {
3551 info.title = componentName.getClassName();
3554 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3556 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3558 info.flags = AppInfo.initFlags(lai);
3563 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3565 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3566 for (ItemInfo i : infos) {
3567 if (i instanceof ShortcutInfo) {
3568 ShortcutInfo info = (ShortcutInfo) i;
3569 ComponentName cn = info.getTargetComponent();
3570 if (cn != null && f.filterItem(null, info, cn)) {
3573 } else if (i instanceof FolderInfo) {
3574 FolderInfo info = (FolderInfo) i;
3575 for (ShortcutInfo s : info.contents) {
3576 ComponentName cn = s.getTargetComponent();
3577 if (cn != null && f.filterItem(info, s, cn)) {
3581 } else if (i instanceof LauncherAppWidgetInfo) {
3582 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3583 ComponentName cn = info.providerName;
3584 if (cn != null && f.filterItem(null, info, cn)) {
3589 return new ArrayList<ItemInfo>(filtered);
3592 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3593 final UserHandleCompat user) {
3594 ItemInfoFilter filter = new ItemInfoFilter() {
3596 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3597 if (info.user == null) {
3598 return cn.equals(cname);
3600 return cn.equals(cname) && info.user.equals(user);
3604 return filterItemInfos(sBgItemsIdMap, filter);
3608 * Make an ShortcutInfo object for a shortcut that isn't an application.
3610 @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
3611 int titleIndex, CursorIconInfo iconInfo) {
3612 final ShortcutInfo info = new ShortcutInfo();
3613 // Non-app shortcuts are only supported for current user.
3614 info.user = UserHandleCompat.myUserHandle();
3615 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3617 // TODO: If there's an explicit component and we can't install that, delete it.
3619 info.title = Utilities.trim(c.getString(titleIndex));
3621 Bitmap icon = iconInfo.loadIcon(c, info, context);
3622 // the fallback icon
3624 icon = mIconCache.getDefaultIcon(info.user);
3625 info.usingFallbackIcon = true;
3631 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3632 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3633 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3634 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3636 if (intent == null) {
3637 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3638 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3643 boolean customIcon = false;
3644 ShortcutIconResource iconResource = null;
3646 if (bitmap instanceof Bitmap) {
3647 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3650 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3651 if (extra instanceof ShortcutIconResource) {
3652 iconResource = (ShortcutIconResource) extra;
3653 icon = Utilities.createIconBitmap(iconResource.packageName,
3654 iconResource.resourceName, context);
3658 final ShortcutInfo info = new ShortcutInfo();
3660 // Only support intents for current user for now. Intents sent from other
3661 // users wouldn't get here without intent forwarding anyway.
3662 info.user = UserHandleCompat.myUserHandle();
3664 icon = mIconCache.getDefaultIcon(info.user);
3665 info.usingFallbackIcon = true;
3669 info.title = Utilities.trim(name);
3670 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3671 info.intent = intent;
3672 info.customIcon = customIcon;
3673 info.iconResource = iconResource;
3679 * Return an existing FolderInfo object if we have encountered this ID previously,
3680 * or make a new one.
3682 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3683 // See if a placeholder was created for us already
3684 FolderInfo folderInfo = folders.get(id);
3685 if (folderInfo == null) {
3686 // No placeholder -- create a new instance
3687 folderInfo = new FolderInfo();
3688 folders.put(id, folderInfo);
3694 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3695 return (provider != null) && (provider.provider != null)
3696 && (provider.provider.getPackageName() != null);
3699 public void dumpState() {
3700 Log.d(TAG, "mCallbacks=" + mCallbacks);
3701 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3702 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3703 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3704 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3705 if (mLoaderTask != null) {
3706 mLoaderTask.dumpState();
3708 Log.d(TAG, "mLoaderTask=null");
3712 public Callbacks getCallback() {
3713 return mCallbacks != null ? mCallbacks.get() : null;
3717 * @return {@link FolderInfo} if its already loaded.
3719 public FolderInfo findFolderById(Long folderId) {
3720 synchronized (sBgLock) {
3721 return sBgFolders.get(folderId);
3726 * @return the looper for the worker thread which can be used to start background tasks.
3728 public static Looper getWorkerLooper() {
3729 return sWorkerThread.getLooper();