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