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