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