LauncherModel.java revision 1cd01b023acc123b771765b7297d8aaedac224e0
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.database.Cursor;
31import android.graphics.Bitmap;
32import android.net.Uri;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.Looper;
36import android.os.Parcelable;
37import android.os.Process;
38import android.os.SystemClock;
39import android.os.Trace;
40import android.provider.BaseColumns;
41import android.text.TextUtils;
42import android.util.Log;
43import android.util.LongSparseArray;
44import android.util.MutableInt;
45
46import com.android.launcher3.compat.AppWidgetManagerCompat;
47import com.android.launcher3.compat.LauncherActivityInfoCompat;
48import com.android.launcher3.compat.LauncherAppsCompat;
49import com.android.launcher3.compat.PackageInstallerCompat;
50import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
51import com.android.launcher3.compat.UserHandleCompat;
52import com.android.launcher3.compat.UserManagerCompat;
53import com.android.launcher3.config.FeatureFlags;
54import com.android.launcher3.config.ProviderConfig;
55import com.android.launcher3.dynamicui.ExtractionUtils;
56import com.android.launcher3.folder.Folder;
57import com.android.launcher3.folder.FolderIcon;
58import com.android.launcher3.graphics.LauncherIcons;
59import com.android.launcher3.logging.FileLog;
60import com.android.launcher3.model.AddWorkspaceItemsTask;
61import com.android.launcher3.model.ExtendedModelTask;
62import com.android.launcher3.model.BgDataModel;
63import com.android.launcher3.model.CacheDataUpdatedTask;
64import com.android.launcher3.model.GridSizeMigrationTask;
65import com.android.launcher3.model.PackageItemInfo;
66import com.android.launcher3.model.SdCardAvailableReceiver;
67import com.android.launcher3.model.WidgetItem;
68import com.android.launcher3.model.PackageInstallStateChangedTask;
69import com.android.launcher3.model.PackageUpdatedTask;
70import com.android.launcher3.model.ShortcutsChangedTask;
71import com.android.launcher3.model.UserLockStateChangedTask;
72import com.android.launcher3.model.WidgetsModel;
73import com.android.launcher3.provider.ImportDataTask;
74import com.android.launcher3.provider.LauncherDbUtils;
75import com.android.launcher3.shortcuts.DeepShortcutManager;
76import com.android.launcher3.shortcuts.ShortcutInfoCompat;
77import com.android.launcher3.shortcuts.ShortcutKey;
78import com.android.launcher3.util.ComponentKey;
79import com.android.launcher3.util.ContentWriter;
80import com.android.launcher3.util.CursorIconInfo;
81import com.android.launcher3.util.GridOccupancy;
82import com.android.launcher3.util.ItemInfoMatcher;
83import com.android.launcher3.util.LongArrayMap;
84import com.android.launcher3.util.ManagedProfileHeuristic;
85import com.android.launcher3.util.MultiHashMap;
86import com.android.launcher3.util.PackageManagerHelper;
87import com.android.launcher3.util.Preconditions;
88import com.android.launcher3.util.Provider;
89import com.android.launcher3.util.Thunk;
90import com.android.launcher3.util.ViewOnDrawExecutor;
91
92import java.lang.ref.WeakReference;
93import java.net.URISyntaxException;
94import java.security.InvalidParameterException;
95import java.util.ArrayList;
96import java.util.Collections;
97import java.util.Comparator;
98import java.util.HashMap;
99import java.util.HashSet;
100import java.util.Iterator;
101import java.util.List;
102import java.util.Map;
103import java.util.Set;
104import java.util.concurrent.Executor;
105
106/**
107 * Maintains in-memory state of the Launcher. It is expected that there should be only one
108 * LauncherModel object held in a static. Also provide APIs for updating the database state
109 * for the Launcher.
110 */
111public class LauncherModel extends BroadcastReceiver
112        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
113    static final boolean DEBUG_LOADERS = false;
114    private static final boolean DEBUG_RECEIVER = false;
115
116    static final String TAG = "Launcher.Model";
117
118    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
119    private static final long INVALID_SCREEN_ID = -1L;
120
121    @Thunk final LauncherAppState mApp;
122    @Thunk final Object mLock = new Object();
123    @Thunk DeferredHandler mHandler = new DeferredHandler();
124    @Thunk LoaderTask mLoaderTask;
125    @Thunk boolean mIsLoaderTaskRunning;
126    @Thunk boolean mHasLoaderCompletedOnce;
127
128    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
129    static {
130        sWorkerThread.start();
131    }
132    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
133
134    // We start off with everything not loaded.  After that, we assume that
135    // our monitoring of the package manager provides all updates and we never
136    // need to do a requery.  These are only ever touched from the loader thread.
137    private boolean mWorkspaceLoaded;
138    private boolean mAllAppsLoaded;
139    private boolean mDeepShortcutsLoaded;
140
141    /**
142     * Set of runnables to be called on the background thread after the workspace binding
143     * is complete.
144     */
145    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
146
147    @Thunk WeakReference<Callbacks> mCallbacks;
148
149    // < only access in worker thread >
150    private final AllAppsList mBgAllAppsList;
151    // Entire list of widgets.
152    private final WidgetsModel mBgWidgetsModel;
153
154    private boolean mHasShortcutHostPermission;
155    // Runnable to check if the shortcuts permission has changed.
156    private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
157        @Override
158        public void run() {
159            if (mDeepShortcutsLoaded) {
160                boolean hasShortcutHostPermission =
161                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
162                if (hasShortcutHostPermission != mHasShortcutHostPermission) {
163                    mApp.reloadWorkspace();
164                }
165            }
166        }
167    };
168
169    /**
170     * All the static data should be accessed on the background thread, A lock should be acquired
171     * on this object when accessing any data from this model.
172     */
173    static final BgDataModel sBgDataModel = new BgDataModel();
174
175    // </ only access in worker thread >
176
177    private final IconCache mIconCache;
178
179    private final LauncherAppsCompat mLauncherApps;
180    private final UserManagerCompat mUserManager;
181
182    public interface Callbacks {
183        public boolean setLoadOnResume();
184        public int getCurrentWorkspaceScreen();
185        public void clearPendingBinds();
186        public void startBinding();
187        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
188                              boolean forceAnimateIcons);
189        public void bindScreens(ArrayList<Long> orderedScreenIds);
190        public void finishFirstPageBind(ViewOnDrawExecutor executor);
191        public void finishBindingItems();
192        public void bindAppWidget(LauncherAppWidgetInfo info);
193        public void bindAllApplications(ArrayList<AppInfo> apps);
194        public void bindAppsAdded(ArrayList<Long> newScreens,
195                                  ArrayList<ItemInfo> addNotAnimated,
196                                  ArrayList<ItemInfo> addAnimated,
197                                  ArrayList<AppInfo> addedApps);
198        public void bindAppsUpdated(ArrayList<AppInfo> apps);
199        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
200                ArrayList<ShortcutInfo> removed, UserHandleCompat user);
201        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
202        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
203        public void bindWorkspaceComponentsRemoved(
204                HashSet<String> packageNames, HashSet<ComponentName> components,
205                UserHandleCompat user);
206        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
207        public void notifyWidgetProvidersChanged();
208        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
209        public void onPageBoundSynchronously(int page);
210        public void executeOnNextDraw(ViewOnDrawExecutor executor);
211        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
212    }
213
214    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
215        Context context = app.getContext();
216        mApp = app;
217        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
218        mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
219        mIconCache = iconCache;
220
221        mLauncherApps = LauncherAppsCompat.getInstance(context);
222        mUserManager = UserManagerCompat.getInstance(context);
223    }
224
225    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
226     * posted on the main thread handler. */
227    private void runOnMainThread(Runnable r) {
228        if (sWorkerThread.getThreadId() == Process.myTid()) {
229            // If we are on the worker thread, post onto the main handler
230            mHandler.post(r);
231        } else {
232            r.run();
233        }
234    }
235
236    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
237     * posted on the worker thread handler. */
238    private static void runOnWorkerThread(Runnable r) {
239        if (sWorkerThread.getThreadId() == Process.myTid()) {
240            r.run();
241        } else {
242            // If we are not on the worker thread, then post to the worker handler
243            sWorker.post(r);
244        }
245    }
246
247    public void setPackageState(PackageInstallInfo installInfo) {
248        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
249    }
250
251    /**
252     * Updates the icons and label of all pending icons for the provided package name.
253     */
254    public void updateSessionDisplayInfo(final String packageName) {
255        HashSet<String> packages = new HashSet<>();
256        packages.add(packageName);
257        enqueueModelUpdateTask(new CacheDataUpdatedTask(
258                CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages));
259    }
260
261    /**
262     * Adds the provided items to the workspace.
263     */
264    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
265        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
266    }
267
268    /**
269     * Adds the provided items to the workspace.
270     */
271    public void addAndBindAddedWorkspaceItems(
272            Provider<List<ItemInfo>> appsProvider) {
273        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
274    }
275
276    /**
277     * Adds an item to the DB if it was not created previously, or move it to a new
278     * <container, screen, cellX, cellY>
279     */
280    public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
281            long screenId, int cellX, int cellY) {
282        if (item.container == ItemInfo.NO_ID) {
283            // From all apps
284            addItemToDatabase(context, item, container, screenId, cellX, cellY);
285        } else {
286            // From somewhere else
287            moveItemInDatabase(context, item, container, screenId, cellX, cellY);
288        }
289    }
290
291    static void checkItemInfoLocked(
292            final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
293        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
294        if (modelItem != null && item != modelItem) {
295            // check all the data is consistent
296            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
297                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
298                ShortcutInfo shortcut = (ShortcutInfo) item;
299                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
300                        modelShortcut.intent.filterEquals(shortcut.intent) &&
301                        modelShortcut.id == shortcut.id &&
302                        modelShortcut.itemType == shortcut.itemType &&
303                        modelShortcut.container == shortcut.container &&
304                        modelShortcut.screenId == shortcut.screenId &&
305                        modelShortcut.cellX == shortcut.cellX &&
306                        modelShortcut.cellY == shortcut.cellY &&
307                        modelShortcut.spanX == shortcut.spanX &&
308                        modelShortcut.spanY == shortcut.spanY) {
309                    // For all intents and purposes, this is the same object
310                    return;
311                }
312            }
313
314            // the modelItem needs to match up perfectly with item if our model is
315            // to be consistent with the database-- for now, just require
316            // modelItem == item or the equality check above
317            String msg = "item: " + ((item != null) ? item.toString() : "null") +
318                    "modelItem: " +
319                    ((modelItem != null) ? modelItem.toString() : "null") +
320                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
321            RuntimeException e = new RuntimeException(msg);
322            if (stackTrace != null) {
323                e.setStackTrace(stackTrace);
324            }
325            throw e;
326        }
327    }
328
329    static void checkItemInfo(final ItemInfo item) {
330        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
331        final long itemId = item.id;
332        Runnable r = new Runnable() {
333            public void run() {
334                synchronized (sBgDataModel) {
335                    checkItemInfoLocked(itemId, item, stackTrace);
336                }
337            }
338        };
339        runOnWorkerThread(r);
340    }
341
342    static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
343            final ItemInfo item, final String callingFunction) {
344        final long itemId = item.id;
345        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
346        final ContentResolver cr = context.getContentResolver();
347
348        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
349        Runnable r = new Runnable() {
350            public void run() {
351                cr.update(uri, writer.getValues(), null, null);
352                updateItemArrays(item, itemId, stackTrace);
353            }
354        };
355        runOnWorkerThread(r);
356    }
357
358    static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
359            final ArrayList<ItemInfo> items, final String callingFunction) {
360        final ContentResolver cr = context.getContentResolver();
361
362        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
363        Runnable r = new Runnable() {
364            public void run() {
365                ArrayList<ContentProviderOperation> ops =
366                        new ArrayList<ContentProviderOperation>();
367                int count = items.size();
368                for (int i = 0; i < count; i++) {
369                    ItemInfo item = items.get(i);
370                    final long itemId = item.id;
371                    final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
372                    ContentValues values = valuesList.get(i);
373
374                    ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
375                    updateItemArrays(item, itemId, stackTrace);
376
377                }
378                try {
379                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
380                } catch (Exception e) {
381                    e.printStackTrace();
382                }
383            }
384        };
385        runOnWorkerThread(r);
386    }
387
388    static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
389        // Lock on mBgLock *after* the db operation
390        synchronized (sBgDataModel) {
391            checkItemInfoLocked(itemId, item, stackTrace);
392
393            if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
394                    item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
395                // Item is in a folder, make sure this folder exists
396                if (!sBgDataModel.folders.containsKey(item.container)) {
397                    // An items container is being set to a that of an item which is not in
398                    // the list of Folders.
399                    String msg = "item: " + item + " container being set to: " +
400                            item.container + ", not in the list of folders";
401                    Log.e(TAG, msg);
402                }
403            }
404
405            // Items are added/removed from the corresponding FolderInfo elsewhere, such
406            // as in Workspace.onDrop. Here, we just add/remove them from the list of items
407            // that are on the desktop, as appropriate
408            ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
409            if (modelItem != null &&
410                    (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
411                     modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
412                switch (modelItem.itemType) {
413                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
414                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
415                    case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
416                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
417                        if (!sBgDataModel.workspaceItems.contains(modelItem)) {
418                            sBgDataModel.workspaceItems.add(modelItem);
419                        }
420                        break;
421                    default:
422                        break;
423                }
424            } else {
425                sBgDataModel.workspaceItems.remove(modelItem);
426            }
427        }
428    }
429
430    /**
431     * Move an item in the DB to a new <container, screen, cellX, cellY>
432     */
433    public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
434            final long screenId, final int cellX, final int cellY) {
435        item.container = container;
436        item.cellX = cellX;
437        item.cellY = cellY;
438
439        // We store hotseat items in canonical form which is this orientation invariant position
440        // in the hotseat
441        if (context instanceof Launcher && screenId < 0 &&
442                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
443            item.screenId = Launcher.getLauncher(context).getHotseat()
444                    .getOrderInHotseat(cellX, cellY);
445        } else {
446            item.screenId = screenId;
447        }
448
449        final ContentWriter writer = new ContentWriter(context)
450                .put(LauncherSettings.Favorites.CONTAINER, item.container)
451                .put(LauncherSettings.Favorites.CELLX, item.cellX)
452                .put(LauncherSettings.Favorites.CELLY, item.cellY)
453                .put(LauncherSettings.Favorites.RANK, item.rank)
454                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
455
456        updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase");
457    }
458
459    /**
460     * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
461     * cellX, cellY have already been updated on the ItemInfos.
462     */
463    public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
464            final long container, final int screen) {
465
466        ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
467        int count = items.size();
468
469        for (int i = 0; i < count; i++) {
470            ItemInfo item = items.get(i);
471            item.container = container;
472
473            // We store hotseat items in canonical form which is this orientation invariant position
474            // in the hotseat
475            if (context instanceof Launcher && screen < 0 &&
476                    container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
477                item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX,
478                        item.cellY);
479            } else {
480                item.screenId = screen;
481            }
482
483            final ContentValues values = new ContentValues();
484            values.put(LauncherSettings.Favorites.CONTAINER, item.container);
485            values.put(LauncherSettings.Favorites.CELLX, item.cellX);
486            values.put(LauncherSettings.Favorites.CELLY, item.cellY);
487            values.put(LauncherSettings.Favorites.RANK, item.rank);
488            values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
489
490            contentValues.add(values);
491        }
492        updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
493    }
494
495    /**
496     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
497     */
498    static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
499            final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
500        item.container = container;
501        item.cellX = cellX;
502        item.cellY = cellY;
503        item.spanX = spanX;
504        item.spanY = spanY;
505
506        // We store hotseat items in canonical form which is this orientation invariant position
507        // in the hotseat
508        if (context instanceof Launcher && screenId < 0 &&
509                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
510            item.screenId = Launcher.getLauncher(context).getHotseat()
511                    .getOrderInHotseat(cellX, cellY);
512        } else {
513            item.screenId = screenId;
514        }
515
516        final ContentWriter writer = new ContentWriter(context)
517                .put(LauncherSettings.Favorites.CONTAINER, item.container)
518                .put(LauncherSettings.Favorites.CELLX, item.cellX)
519                .put(LauncherSettings.Favorites.CELLY, item.cellY)
520                .put(LauncherSettings.Favorites.RANK, item.rank)
521                .put(LauncherSettings.Favorites.SPANX, item.spanX)
522                .put(LauncherSettings.Favorites.SPANY, item.spanY)
523                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
524
525        updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase");
526    }
527
528    /**
529     * Update an item to the database in a specified container.
530     */
531    public static void updateItemInDatabase(Context context, final ItemInfo item) {
532        ContentWriter writer = new ContentWriter(context);
533        item.onAddToDatabase(writer);
534        updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase");
535    }
536
537    /**
538     * Add an item to the database in a specified container. Sets the container, screen, cellX and
539     * cellY fields of the item. Also assigns an ID to the item.
540     */
541    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
542            final long screenId, final int cellX, final int cellY) {
543        item.container = container;
544        item.cellX = cellX;
545        item.cellY = cellY;
546        // We store hotseat items in canonical form which is this orientation invariant position
547        // in the hotseat
548        if (context instanceof Launcher && screenId < 0 &&
549                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
550            item.screenId = Launcher.getLauncher(context).getHotseat()
551                    .getOrderInHotseat(cellX, cellY);
552        } else {
553            item.screenId = screenId;
554        }
555
556        final ContentWriter writer = new ContentWriter(context);
557        final ContentResolver cr = context.getContentResolver();
558        item.onAddToDatabase(writer);
559
560        item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
561                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
562
563        writer.put(LauncherSettings.Favorites._ID, item.id);
564
565        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
566        Runnable r = new Runnable() {
567            public void run() {
568                cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
569
570                synchronized (sBgDataModel) {
571                    checkItemInfoLocked(item.id, item, stackTrace);
572                    sBgDataModel.addItem(item, true);
573                }
574            }
575        };
576        runOnWorkerThread(r);
577    }
578
579    /**
580     * Removes the specified item from the database
581     */
582    public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
583        ArrayList<ItemInfo> items = new ArrayList<>();
584        items.add(item);
585        deleteItemsFromDatabase(context, items);
586    }
587
588    /**
589     * Removes all the items from the database matching {@param matcher}.
590     */
591    public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) {
592        deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap));
593    }
594
595    /**
596     * Removes the specified items from the database
597     */
598    public static void deleteItemsFromDatabase(Context context,
599            final Iterable<? extends ItemInfo> items) {
600        final ContentResolver cr = context.getContentResolver();
601        Runnable r = new Runnable() {
602            public void run() {
603                for (ItemInfo item : items) {
604                    final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
605                    cr.delete(uri, null, null);
606
607                    sBgDataModel.removeItem(item);
608                }
609            }
610        };
611        runOnWorkerThread(r);
612    }
613
614    /**
615     * Update the order of the workspace screens in the database. The array list contains
616     * a list of screen ids in the order that they should appear.
617     */
618    public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
619        final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
620        final ContentResolver cr = context.getContentResolver();
621        final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
622
623        // Remove any negative screen ids -- these aren't persisted
624        Iterator<Long> iter = screensCopy.iterator();
625        while (iter.hasNext()) {
626            long id = iter.next();
627            if (id < 0) {
628                iter.remove();
629            }
630        }
631
632        Runnable r = new Runnable() {
633            @Override
634            public void run() {
635                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
636                // Clear the table
637                ops.add(ContentProviderOperation.newDelete(uri).build());
638                int count = screensCopy.size();
639                for (int i = 0; i < count; i++) {
640                    ContentValues v = new ContentValues();
641                    long screenId = screensCopy.get(i);
642                    v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
643                    v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
644                    ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
645                }
646
647                try {
648                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
649                } catch (Exception ex) {
650                    throw new RuntimeException(ex);
651                }
652
653                synchronized (sBgDataModel) {
654                    sBgDataModel.workspaceScreens.clear();
655                    sBgDataModel.workspaceScreens.addAll(screensCopy);
656                }
657            }
658        };
659        runOnWorkerThread(r);
660    }
661
662    /**
663     * Remove the specified folder and all its contents from the database.
664     */
665    public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
666        final ContentResolver cr = context.getContentResolver();
667
668        Runnable r = new Runnable() {
669            public void run() {
670                cr.delete(LauncherSettings.Favorites.CONTENT_URI,
671                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
672                sBgDataModel.removeItem(info.contents);
673                info.contents.clear();
674
675                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
676                sBgDataModel.removeItem(info);
677            }
678        };
679        runOnWorkerThread(r);
680    }
681
682    /**
683     * Set this as the current Launcher activity object for the loader.
684     */
685    public void initialize(Callbacks callbacks) {
686        synchronized (mLock) {
687            Preconditions.assertUIThread();
688            // Remove any queued UI runnables
689            mHandler.cancelAll();
690            mCallbacks = new WeakReference<>(callbacks);
691        }
692    }
693
694    @Override
695    public void onPackageChanged(String packageName, UserHandleCompat user) {
696        int op = PackageUpdatedTask.OP_UPDATE;
697        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
698    }
699
700    @Override
701    public void onPackageRemoved(String packageName, UserHandleCompat user) {
702        onPackagesRemoved(user, packageName);
703    }
704
705    public void onPackagesRemoved(UserHandleCompat user, String... packages) {
706        int op = PackageUpdatedTask.OP_REMOVE;
707        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
708    }
709
710    @Override
711    public void onPackageAdded(String packageName, UserHandleCompat user) {
712        int op = PackageUpdatedTask.OP_ADD;
713        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
714    }
715
716    @Override
717    public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
718            boolean replacing) {
719        enqueueModelUpdateTask(
720                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
721    }
722
723    @Override
724    public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
725            boolean replacing) {
726        if (!replacing) {
727            enqueueModelUpdateTask(new PackageUpdatedTask(
728                    PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
729        }
730    }
731
732    @Override
733    public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
734        enqueueModelUpdateTask(new PackageUpdatedTask(
735                PackageUpdatedTask.OP_SUSPEND, user, packageNames));
736    }
737
738    @Override
739    public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
740        enqueueModelUpdateTask(new PackageUpdatedTask(
741                PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
742    }
743
744    @Override
745    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
746            UserHandleCompat user) {
747        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
748    }
749
750    public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
751            UserHandleCompat user) {
752        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
753    }
754
755    /**
756     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
757     * ACTION_PACKAGE_CHANGED.
758     */
759    @Override
760    public void onReceive(Context context, Intent intent) {
761        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
762
763        final String action = intent.getAction();
764        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
765            // If we have changed locale we need to clear out the labels in all apps/workspace.
766            forceReload();
767        } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
768                || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
769            UserManagerCompat.getInstance(context).enableAndResetCache();
770            forceReload();
771        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
772                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
773                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
774            UserHandleCompat user = UserHandleCompat.fromIntent(intent);
775            if (user != null) {
776                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
777                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
778                    enqueueModelUpdateTask(new PackageUpdatedTask(
779                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
780                }
781
782                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
783                // we need to run the state change task again.
784                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
785                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
786                    enqueueModelUpdateTask(new UserLockStateChangedTask(user));
787                }
788            }
789        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
790            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
791        }
792    }
793
794    void forceReload() {
795        resetLoadedState(true, true);
796
797        // Do this here because if the launcher activity is running it will be restarted.
798        // If it's not running startLoaderFromBackground will merely tell it that it needs
799        // to reload.
800        startLoaderFromBackground();
801    }
802
803    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
804        synchronized (mLock) {
805            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
806            // mWorkspaceLoaded to true later
807            stopLoaderLocked();
808            if (resetAllAppsLoaded) mAllAppsLoaded = false;
809            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
810            // Always reset deep shortcuts loaded.
811            // TODO: why?
812            mDeepShortcutsLoaded = false;
813        }
814    }
815
816    /**
817     * When the launcher is in the background, it's possible for it to miss paired
818     * configuration changes.  So whenever we trigger the loader from the background
819     * tell the launcher that it needs to re-run the loader when it comes back instead
820     * of doing it now.
821     */
822    public void startLoaderFromBackground() {
823        Callbacks callbacks = getCallback();
824        if (callbacks != null) {
825            // Only actually run the loader if they're not paused.
826            if (!callbacks.setLoadOnResume()) {
827                startLoader(callbacks.getCurrentWorkspaceScreen());
828            }
829        }
830    }
831
832    /**
833     * If there is already a loader task running, tell it to stop.
834     */
835    private void stopLoaderLocked() {
836        LoaderTask oldTask = mLoaderTask;
837        if (oldTask != null) {
838            oldTask.stopLocked();
839        }
840    }
841
842    public boolean isCurrentCallbacks(Callbacks callbacks) {
843        return (mCallbacks != null && mCallbacks.get() == callbacks);
844    }
845
846    /**
847     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
848     * @return true if the page could be bound synchronously.
849     */
850    public boolean startLoader(int synchronousBindPage) {
851        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
852        InstallShortcutReceiver.enableInstallQueue();
853        synchronized (mLock) {
854            // Don't bother to start the thread if we know it's not going to do anything
855            if (mCallbacks != null && mCallbacks.get() != null) {
856                final Callbacks oldCallbacks = mCallbacks.get();
857                // Clear any pending bind-runnables from the synchronized load process.
858                runOnMainThread(new Runnable() {
859                    public void run() {
860                        oldCallbacks.clearPendingBinds();
861                    }
862                });
863
864                // If there is already one running, tell it to stop.
865                stopLoaderLocked();
866                mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
867                // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
868                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
869                        && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
870                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
871                    return true;
872                } else {
873                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
874                    sWorker.post(mLoaderTask);
875                }
876            }
877        }
878        return false;
879    }
880
881    public void stopLoader() {
882        synchronized (mLock) {
883            if (mLoaderTask != null) {
884                mLoaderTask.stopLocked();
885            }
886        }
887    }
888
889    /**
890     * Loads the workspace screen ids in an ordered list.
891     */
892    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
893        final ContentResolver contentResolver = context.getContentResolver();
894        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
895
896        // Get screens ordered by rank.
897        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
898                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
899    }
900
901    /**
902     * Runnable for the thread that loads the contents of the launcher:
903     *   - workspace icons
904     *   - widgets
905     *   - all apps icons
906     *   - deep shortcuts within apps
907     */
908    private class LoaderTask implements Runnable {
909        private Context mContext;
910        private int mPageToBindFirst;
911
912        @Thunk boolean mIsLoadingAndBindingWorkspace;
913        private boolean mStopped;
914        @Thunk boolean mLoadAndBindStepFinished;
915
916        LoaderTask(Context context, int pageToBindFirst) {
917            mContext = context;
918            mPageToBindFirst = pageToBindFirst;
919        }
920
921        private void loadAndBindWorkspace() {
922            mIsLoadingAndBindingWorkspace = true;
923
924            // Load the workspace
925            if (DEBUG_LOADERS) {
926                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
927            }
928
929            if (!mWorkspaceLoaded) {
930                loadWorkspace();
931                synchronized (LoaderTask.this) {
932                    if (mStopped) {
933                        return;
934                    }
935                    mWorkspaceLoaded = true;
936                }
937            }
938
939            // Bind the workspace
940            bindWorkspace(mPageToBindFirst);
941        }
942
943        private void waitForIdle() {
944            // Wait until the either we're stopped or the other threads are done.
945            // This way we don't start loading all apps until the workspace has settled
946            // down.
947            synchronized (LoaderTask.this) {
948                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
949
950                mHandler.postIdle(new Runnable() {
951                        public void run() {
952                            synchronized (LoaderTask.this) {
953                                mLoadAndBindStepFinished = true;
954                                if (DEBUG_LOADERS) {
955                                    Log.d(TAG, "done with previous binding step");
956                                }
957                                LoaderTask.this.notify();
958                            }
959                        }
960                    });
961
962                while (!mStopped && !mLoadAndBindStepFinished) {
963                    try {
964                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
965                        // wait no longer than 1sec at a time
966                        this.wait(1000);
967                    } catch (InterruptedException ex) {
968                        // Ignore
969                    }
970                }
971                if (DEBUG_LOADERS) {
972                    Log.d(TAG, "waited "
973                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
974                            + "ms for previous step to finish binding");
975                }
976            }
977        }
978
979        void runBindSynchronousPage(int synchronousBindPage) {
980            if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
981                // Ensure that we have a valid page index to load synchronously
982                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
983                        "valid page index");
984            }
985            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
986                // Ensure that we don't try and bind a specified page when the pages have not been
987                // loaded already (we should load everything asynchronously in that case)
988                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
989            }
990            synchronized (mLock) {
991                if (mIsLoaderTaskRunning) {
992                    // Ensure that we are never running the background loading at this point since
993                    // we also touch the background collections
994                    throw new RuntimeException("Error! Background loading is already running");
995                }
996            }
997
998            // XXX: Throw an exception if we are already loading (since we touch the worker thread
999            //      data structures, we can't allow any other thread to touch that data, but because
1000            //      this call is synchronous, we can get away with not locking).
1001
1002            // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1003            // operations from the previous activity.  We need to ensure that all queued operations
1004            // are executed before any synchronous binding work is done.
1005            mHandler.flush();
1006
1007            // Divide the set of loaded items into those that we are binding synchronously, and
1008            // everything else that is to be bound normally (asynchronously).
1009            bindWorkspace(synchronousBindPage);
1010            // XXX: For now, continue posting the binding of AllApps as there are other issues that
1011            //      arise from that.
1012            onlyBindAllApps();
1013
1014            bindDeepShortcuts();
1015        }
1016
1017        public void run() {
1018            synchronized (mLock) {
1019                if (mStopped) {
1020                    return;
1021                }
1022                mIsLoaderTaskRunning = true;
1023            }
1024            // Optimize for end-user experience: if the Launcher is up and // running with the
1025            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1026            // workspace first (default).
1027            keep_running: {
1028                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1029                loadAndBindWorkspace();
1030
1031                if (mStopped) {
1032                    break keep_running;
1033                }
1034
1035                waitForIdle();
1036
1037                // second step
1038                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1039                loadAndBindAllApps();
1040
1041                waitForIdle();
1042
1043                // third step
1044                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
1045                loadAndBindDeepShortcuts();
1046            }
1047
1048            // Clear out this reference, otherwise we end up holding it until all of the
1049            // callback runnables are done.
1050            mContext = null;
1051
1052            synchronized (mLock) {
1053                // If we are still the last one to be scheduled, remove ourselves.
1054                if (mLoaderTask == this) {
1055                    mLoaderTask = null;
1056                }
1057                mIsLoaderTaskRunning = false;
1058                mHasLoaderCompletedOnce = true;
1059            }
1060        }
1061
1062        public void stopLocked() {
1063            synchronized (LoaderTask.this) {
1064                mStopped = true;
1065                this.notify();
1066            }
1067        }
1068
1069        /**
1070         * Gets the callbacks object.  If we've been stopped, or if the launcher object
1071         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
1072         * object that was around when the deferred message was scheduled, and if there's
1073         * a new Callbacks object around then also return null.  This will save us from
1074         * calling onto it with data that will be ignored.
1075         */
1076        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1077            synchronized (mLock) {
1078                if (mStopped) {
1079                    return null;
1080                }
1081
1082                if (mCallbacks == null) {
1083                    return null;
1084                }
1085
1086                final Callbacks callbacks = mCallbacks.get();
1087                if (callbacks != oldCallbacks) {
1088                    return null;
1089                }
1090                if (callbacks == null) {
1091                    Log.w(TAG, "no mCallbacks");
1092                    return null;
1093                }
1094
1095                return callbacks;
1096            }
1097        }
1098
1099        // check & update map of what's occupied; used to discard overlapping/invalid items
1100        private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
1101                   ArrayList<Long> workspaceScreens) {
1102            LauncherAppState app = LauncherAppState.getInstance();
1103            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1104
1105            long containerIndex = item.screenId;
1106            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1107                // Return early if we detect that an item is under the hotseat button
1108                if (!FeatureFlags.NO_ALL_APPS_ICON &&
1109                        profile.isAllAppsButtonRank((int) item.screenId)) {
1110                    Log.e(TAG, "Error loading shortcut into hotseat " + item
1111                            + " into position (" + item.screenId + ":" + item.cellX + ","
1112                            + item.cellY + ") occupied by all apps");
1113                    return false;
1114                }
1115
1116                final GridOccupancy hotseatOccupancy =
1117                        occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1118
1119                if (item.screenId >= profile.numHotseatIcons) {
1120                    Log.e(TAG, "Error loading shortcut " + item
1121                            + " into hotseat position " + item.screenId
1122                            + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1123                            + ")");
1124                    return false;
1125                }
1126
1127                if (hotseatOccupancy != null) {
1128                    if (hotseatOccupancy.cells[(int) item.screenId][0]) {
1129                        Log.e(TAG, "Error loading shortcut into hotseat " + item
1130                                + " into position (" + item.screenId + ":" + item.cellX + ","
1131                                + item.cellY + ") already occupied");
1132                            return false;
1133                    } else {
1134                        hotseatOccupancy.cells[(int) item.screenId][0] = true;
1135                        return true;
1136                    }
1137                } else {
1138                    final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
1139                    occupancy.cells[(int) item.screenId][0] = true;
1140                    occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
1141                    return true;
1142                }
1143            } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1144                if (!workspaceScreens.contains((Long) item.screenId)) {
1145                    // The item has an invalid screen id.
1146                    return false;
1147                }
1148            } else {
1149                // Skip further checking if it is not the hotseat or workspace container
1150                return true;
1151            }
1152
1153            final int countX = profile.numColumns;
1154            final int countY = profile.numRows;
1155            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1156                    item.cellX < 0 || item.cellY < 0 ||
1157                    item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1158                Log.e(TAG, "Error loading shortcut " + item
1159                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
1160                        + item.cellX + "," + item.cellY
1161                        + ") out of screen bounds ( " + countX + "x" + countY + ")");
1162                return false;
1163            }
1164
1165            if (!occupied.containsKey(item.screenId)) {
1166                GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
1167                if (item.screenId == Workspace.FIRST_SCREEN_ID) {
1168                    // Mark the first row as occupied (if the feature is enabled)
1169                    // in order to account for the QSB.
1170                    screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
1171                }
1172                occupied.put(item.screenId, screen);
1173            }
1174            final GridOccupancy occupancy = occupied.get(item.screenId);
1175
1176            // Check if any workspace icons overlap with each other
1177            if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
1178                occupancy.markCells(item, true);
1179                return true;
1180            } else {
1181                Log.e(TAG, "Error loading shortcut " + item
1182                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
1183                        + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
1184                        + ") already occupied");
1185                return false;
1186            }
1187        }
1188
1189        private void loadWorkspace() {
1190            if (LauncherAppState.PROFILE_STARTUP) {
1191                Trace.beginSection("Loading Workspace");
1192            }
1193            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1194
1195            final Context context = mContext;
1196            final ContentResolver contentResolver = context.getContentResolver();
1197            final PackageManager manager = context.getPackageManager();
1198            final boolean isSafeMode = manager.isSafeMode();
1199            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1200            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
1201            final boolean isSdCardReady = Utilities.isBootCompleted();
1202            final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>();
1203
1204            LauncherAppState app = LauncherAppState.getInstance();
1205            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1206            int countX = profile.numColumns;
1207            int countY = profile.numRows;
1208
1209            boolean clearDb = false;
1210            try {
1211                ImportDataTask.performImportIfPossible(context);
1212            } catch (Exception e) {
1213                // Migration failed. Clear workspace.
1214                clearDb = true;
1215            }
1216
1217            if (!clearDb && GridSizeMigrationTask.ENABLED &&
1218                    !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
1219                // Migration failed. Clear workspace.
1220                clearDb = true;
1221            }
1222
1223            if (clearDb) {
1224                Log.d(TAG, "loadWorkspace: resetting launcher database");
1225                LauncherSettings.Settings.call(contentResolver,
1226                        LauncherSettings.Settings.METHOD_DELETE_DB);
1227            }
1228
1229            Log.d(TAG, "loadWorkspace: loading default favorites");
1230            LauncherSettings.Settings.call(contentResolver,
1231                    LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
1232
1233            synchronized (sBgDataModel) {
1234                sBgDataModel.clear();
1235
1236                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1237                        .getInstance(mContext).updateAndGetActiveSessionCache();
1238                sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
1239
1240                final ArrayList<Long> itemsToRemove = new ArrayList<>();
1241                final ArrayList<Long> restoredRows = new ArrayList<>();
1242                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
1243                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1244                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1245                final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1246
1247                // +1 for the hotseat (it can be larger than the workspace)
1248                // Load workspace in reverse order to ensure that latest items are loaded first (and
1249                // before any earlier duplicates)
1250                final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
1251                HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
1252
1253                try {
1254                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1255                    final int intentIndex = c.getColumnIndexOrThrow
1256                            (LauncherSettings.Favorites.INTENT);
1257                    final int containerIndex = c.getColumnIndexOrThrow(
1258                            LauncherSettings.Favorites.CONTAINER);
1259                    final int itemTypeIndex = c.getColumnIndexOrThrow(
1260                            LauncherSettings.Favorites.ITEM_TYPE);
1261                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1262                            LauncherSettings.Favorites.APPWIDGET_ID);
1263                    final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1264                            LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1265                    final int screenIndex = c.getColumnIndexOrThrow(
1266                            LauncherSettings.Favorites.SCREEN);
1267                    final int cellXIndex = c.getColumnIndexOrThrow
1268                            (LauncherSettings.Favorites.CELLX);
1269                    final int cellYIndex = c.getColumnIndexOrThrow
1270                            (LauncherSettings.Favorites.CELLY);
1271                    final int spanXIndex = c.getColumnIndexOrThrow
1272                            (LauncherSettings.Favorites.SPANX);
1273                    final int spanYIndex = c.getColumnIndexOrThrow(
1274                            LauncherSettings.Favorites.SPANY);
1275                    final int rankIndex = c.getColumnIndexOrThrow(
1276                            LauncherSettings.Favorites.RANK);
1277                    final int restoredIndex = c.getColumnIndexOrThrow(
1278                            LauncherSettings.Favorites.RESTORED);
1279                    final int profileIdIndex = c.getColumnIndexOrThrow(
1280                            LauncherSettings.Favorites.PROFILE_ID);
1281                    final int optionsIndex = c.getColumnIndexOrThrow(
1282                            LauncherSettings.Favorites.OPTIONS);
1283                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
1284
1285                    final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1286                    final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
1287                    final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
1288                    for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1289                        long serialNo = mUserManager.getSerialNumberForUser(user);
1290                        allUsers.put(serialNo, user);
1291                        quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
1292
1293                        boolean userUnlocked = mUserManager.isUserUnlocked(user);
1294
1295                        // We can only query for shortcuts when the user is unlocked.
1296                        if (userUnlocked) {
1297                            List<ShortcutInfoCompat> pinnedShortcuts =
1298                                    shortcutManager.queryForPinnedShortcuts(null, user);
1299                            if (shortcutManager.wasLastCallSuccess()) {
1300                                for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
1301                                    shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
1302                                            shortcut);
1303                                }
1304                            } else {
1305                                // Shortcut manager can fail due to some race condition when the
1306                                // lock state changes too frequently. For the purpose of the loading
1307                                // shortcuts, consider the user is still locked.
1308                                userUnlocked = false;
1309                            }
1310                        }
1311                        unlockedUsers.put(serialNo, userUnlocked);
1312                    }
1313
1314                    ShortcutInfo info;
1315                    String intentDescription;
1316                    LauncherAppWidgetInfo appWidgetInfo;
1317                    int container;
1318                    long id;
1319                    long serialNumber;
1320                    Intent intent;
1321                    UserHandleCompat user;
1322                    String targetPackage;
1323
1324                    while (!mStopped && c.moveToNext()) {
1325                        try {
1326                            int itemType = c.getInt(itemTypeIndex);
1327                            boolean restored = 0 != c.getInt(restoredIndex);
1328                            boolean allowMissingTarget = false;
1329                            container = c.getInt(containerIndex);
1330
1331                            switch (itemType) {
1332                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1333                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1334                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1335                                id = c.getLong(idIndex);
1336                                intentDescription = c.getString(intentIndex);
1337                                serialNumber = c.getInt(profileIdIndex);
1338                                user = allUsers.get(serialNumber);
1339                                int promiseType = c.getInt(restoredIndex);
1340                                int disabledState = 0;
1341                                boolean itemReplaced = false;
1342                                targetPackage = null;
1343                                if (user == null) {
1344                                    // User has been deleted remove the item.
1345                                    itemsToRemove.add(id);
1346                                    continue;
1347                                }
1348                                try {
1349                                    intent = Intent.parseUri(intentDescription, 0);
1350                                    ComponentName cn = intent.getComponent();
1351                                    if (cn != null && cn.getPackageName() != null) {
1352                                        boolean validPkg = launcherApps.isPackageEnabledForProfile(
1353                                                cn.getPackageName(), user);
1354                                        boolean validComponent = validPkg &&
1355                                                launcherApps.isActivityEnabledForProfile(cn, user);
1356                                        if (validPkg) {
1357                                            targetPackage = cn.getPackageName();
1358                                        }
1359
1360                                        if (validComponent) {
1361                                            if (restored) {
1362                                                // no special handling necessary for this item
1363                                                restoredRows.add(id);
1364                                                restored = false;
1365                                            }
1366                                            if (quietMode.get(serialNumber)) {
1367                                                disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
1368                                            }
1369                                        } else if (validPkg) {
1370                                            intent = null;
1371                                            if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1372                                                // We allow auto install apps to have their intent
1373                                                // updated after an install.
1374                                                intent = manager.getLaunchIntentForPackage(
1375                                                        cn.getPackageName());
1376                                                if (intent != null) {
1377                                                    ContentValues values = new ContentValues();
1378                                                    values.put(LauncherSettings.Favorites.INTENT,
1379                                                            intent.toUri(0));
1380                                                    updateItem(id, values);
1381                                                }
1382                                            }
1383
1384                                            if (intent == null) {
1385                                                // The app is installed but the component is no
1386                                                // longer available.
1387                                                FileLog.d(TAG, "Invalid component removed: " + cn);
1388                                                itemsToRemove.add(id);
1389                                                continue;
1390                                            } else {
1391                                                // no special handling necessary for this item
1392                                                restoredRows.add(id);
1393                                                restored = false;
1394                                            }
1395                                        } else if (restored) {
1396                                            // Package is not yet available but might be
1397                                            // installed later.
1398                                            FileLog.d(TAG, "package not yet restored: " + cn);
1399
1400                                            if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1401                                                // Restore has started once.
1402                                            } else if (installingPkgs.containsKey(cn.getPackageName())) {
1403                                                // App restore has started. Update the flag
1404                                                promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1405                                                ContentValues values = new ContentValues();
1406                                                values.put(LauncherSettings.Favorites.RESTORED,
1407                                                        promiseType);
1408                                                updateItem(id, values);
1409                                            } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
1410                                                // This is a common app. Try to replace this.
1411                                                int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
1412                                                CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
1413                                                if (parser.findDefaultApp()) {
1414                                                    // Default app found. Replace it.
1415                                                    intent = parser.parsedIntent;
1416                                                    cn = intent.getComponent();
1417                                                    ContentValues values = parser.parsedValues;
1418                                                    values.put(LauncherSettings.Favorites.RESTORED, 0);
1419                                                    updateItem(id, values);
1420                                                    restored = false;
1421                                                    itemReplaced = true;
1422
1423                                                } else {
1424                                                    FileLog.d(TAG, "Unrestored package removed: " + cn);
1425                                                    itemsToRemove.add(id);
1426                                                    continue;
1427                                                }
1428                                            } else {
1429                                                FileLog.d(TAG, "Unrestored package removed: " + cn);
1430                                                itemsToRemove.add(id);
1431                                                continue;
1432                                            }
1433                                        } else if (PackageManagerHelper.isAppOnSdcard(
1434                                                manager, cn.getPackageName())) {
1435                                            // Package is present but not available.
1436                                            allowMissingTarget = true;
1437                                            disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1438                                        } else if (!isSdCardReady) {
1439                                            // SdCard is not ready yet. Package might get available,
1440                                            // once it is ready.
1441                                            Log.d(TAG, "Invalid package: " + cn + " (check again later)");
1442                                            pendingPackages.addToList(user, cn.getPackageName());
1443                                            allowMissingTarget = true;
1444                                            // Add the icon on the workspace anyway.
1445
1446                                        } else {
1447                                            // Do not wait for external media load anymore.
1448                                            // Log the invalid package, and remove it
1449                                            FileLog.d(TAG, "Invalid package removed: " + cn);
1450                                            itemsToRemove.add(id);
1451                                            continue;
1452                                        }
1453                                    } else if (cn == null) {
1454                                        // For shortcuts with no component, keep them as they are
1455                                        restoredRows.add(id);
1456                                        restored = false;
1457                                    }
1458                                } catch (URISyntaxException e) {
1459                                    FileLog.d(TAG, "Invalid uri: " + intentDescription);
1460                                    itemsToRemove.add(id);
1461                                    continue;
1462                                }
1463
1464                                boolean useLowResIcon = container >= 0 &&
1465                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1466
1467                                if (itemReplaced) {
1468                                    if (user.equals(UserHandleCompat.myUserHandle())) {
1469                                        info = getAppShortcutInfo(intent, user, null,
1470                                                cursorIconInfo, false, useLowResIcon);
1471                                    } else {
1472                                        // Don't replace items for other profiles.
1473                                        itemsToRemove.add(id);
1474                                        continue;
1475                                    }
1476                                } else if (restored) {
1477                                    if (user.equals(UserHandleCompat.myUserHandle())) {
1478                                        info = getRestoredItemInfo(c, intent,
1479                                                promiseType, itemType, cursorIconInfo);
1480                                        intent = getRestoredItemIntent(c, context, intent);
1481                                    } else {
1482                                        // Don't restore items for other profiles.
1483                                        itemsToRemove.add(id);
1484                                        continue;
1485                                    }
1486                                } else if (itemType ==
1487                                        LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1488                                    info = getAppShortcutInfo(intent, user, c,
1489                                            cursorIconInfo, allowMissingTarget, useLowResIcon);
1490                                } else if (itemType ==
1491                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1492
1493                                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
1494                                    if (unlockedUsers.get(serialNumber)) {
1495                                        ShortcutInfoCompat pinnedShortcut =
1496                                                shortcutKeyToPinnedShortcuts.get(key);
1497                                        if (pinnedShortcut == null) {
1498                                            // The shortcut is no longer valid.
1499                                            itemsToRemove.add(id);
1500                                            continue;
1501                                        }
1502                                        info = new ShortcutInfo(pinnedShortcut, context);
1503                                        intent = info.intent;
1504                                    } else {
1505                                        // Create a shortcut info in disabled mode for now.
1506                                        info = new ShortcutInfo();
1507                                        info.user = user;
1508                                        info.itemType = itemType;
1509                                        loadInfoFromCursor(info, c, cursorIconInfo);
1510
1511                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1512                                    }
1513                                } else { // item type == ITEM_TYPE_SHORTCUT
1514                                    info = getShortcutInfo(c, cursorIconInfo);
1515
1516                                    // Shortcuts are only available on the primary profile
1517                                    if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
1518                                        disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1519                                    }
1520
1521                                    // App shortcuts that used to be automatically added to Launcher
1522                                    // didn't always have the correct intent flags set, so do that
1523                                    // here
1524                                    if (intent.getAction() != null &&
1525                                        intent.getCategories() != null &&
1526                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
1527                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1528                                        intent.addFlags(
1529                                            Intent.FLAG_ACTIVITY_NEW_TASK |
1530                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1531                                    }
1532                                }
1533
1534                                if (info != null) {
1535                                    info.id = id;
1536                                    info.intent = intent;
1537                                    info.container = container;
1538                                    info.screenId = c.getInt(screenIndex);
1539                                    info.cellX = c.getInt(cellXIndex);
1540                                    info.cellY = c.getInt(cellYIndex);
1541                                    info.rank = c.getInt(rankIndex);
1542                                    info.spanX = 1;
1543                                    info.spanY = 1;
1544                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1545                                    if (info.promisedIntent != null) {
1546                                        info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1547                                    }
1548                                    info.isDisabled |= disabledState;
1549                                    if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
1550                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1551                                    }
1552
1553                                    // check & update map of what's occupied
1554                                    if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) {
1555                                        itemsToRemove.add(id);
1556                                        break;
1557                                    }
1558
1559                                    if (restored) {
1560                                        ComponentName cn = info.getTargetComponent();
1561                                        if (cn != null) {
1562                                            Integer progress = installingPkgs.get(cn.getPackageName());
1563                                            if (progress != null) {
1564                                                info.setInstallProgress(progress);
1565                                            } else {
1566                                                info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
1567                                            }
1568                                        }
1569                                    }
1570
1571                                    sBgDataModel.addItem(info, false);
1572                                } else {
1573                                    throw new RuntimeException("Unexpected null ShortcutInfo");
1574                                }
1575                                break;
1576
1577                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1578                                id = c.getLong(idIndex);
1579                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id);
1580
1581                                // Do not trim the folder label, as is was set by the user.
1582                                folderInfo.title = c.getString(cursorIconInfo.titleIndex);
1583                                folderInfo.id = id;
1584                                folderInfo.container = container;
1585                                folderInfo.screenId = c.getInt(screenIndex);
1586                                folderInfo.cellX = c.getInt(cellXIndex);
1587                                folderInfo.cellY = c.getInt(cellYIndex);
1588                                folderInfo.spanX = 1;
1589                                folderInfo.spanY = 1;
1590                                folderInfo.options = c.getInt(optionsIndex);
1591
1592                                // check & update map of what's occupied
1593                                if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) {
1594                                    itemsToRemove.add(id);
1595                                    break;
1596                                }
1597                                if (restored) {
1598                                    // no special handling required for restored folders
1599                                    restoredRows.add(id);
1600                                }
1601
1602                                sBgDataModel.addItem(folderInfo, false);
1603                                break;
1604
1605                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1606                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1607                                // Read all Launcher-specific widget details
1608                                boolean customWidget = itemType ==
1609                                    LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1610
1611                                int appWidgetId = c.getInt(appWidgetIdIndex);
1612                                serialNumber = c.getLong(profileIdIndex);
1613                                String savedProvider = c.getString(appWidgetProviderIndex);
1614                                id = c.getLong(idIndex);
1615                                user = allUsers.get(serialNumber);
1616                                if (user == null) {
1617                                    itemsToRemove.add(id);
1618                                    continue;
1619                                }
1620
1621                                final ComponentName component =
1622                                        ComponentName.unflattenFromString(savedProvider);
1623
1624                                final int restoreStatus = c.getInt(restoredIndex);
1625                                final boolean isIdValid = (restoreStatus &
1626                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
1627                                final boolean wasProviderReady = (restoreStatus &
1628                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
1629
1630                                if (widgetProvidersMap == null) {
1631                                    widgetProvidersMap = AppWidgetManagerCompat
1632                                            .getInstance(mContext).getAllProvidersMap();
1633                                }
1634                                final AppWidgetProviderInfo provider = widgetProvidersMap.get(
1635                                        new ComponentKey(
1636                                                ComponentName.unflattenFromString(savedProvider),
1637                                                user));
1638
1639                                final boolean isProviderReady = isValidProvider(provider);
1640                                if (!isSafeMode && !customWidget &&
1641                                        wasProviderReady && !isProviderReady) {
1642                                    FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
1643                                            + provider);
1644                                    itemsToRemove.add(id);
1645                                } else {
1646                                    if (isProviderReady) {
1647                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1648                                                provider.provider);
1649
1650                                        // The provider is available. So the widget is either
1651                                        // available or not available. We do not need to track
1652                                        // any future restore updates.
1653                                        int status = restoreStatus &
1654                                                ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1655                                        if (!wasProviderReady) {
1656                                            // If provider was not previously ready, update the
1657                                            // status and UI flag.
1658
1659                                            // Id would be valid only if the widget restore broadcast was received.
1660                                            if (isIdValid) {
1661                                                status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
1662                                            } else {
1663                                                status &= ~LauncherAppWidgetInfo
1664                                                        .FLAG_PROVIDER_NOT_READY;
1665                                            }
1666                                        }
1667                                        appWidgetInfo.restoreStatus = status;
1668                                    } else {
1669                                        Log.v(TAG, "Widget restore pending id=" + id
1670                                                + " appWidgetId=" + appWidgetId
1671                                                + " status =" + restoreStatus);
1672                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1673                                                component);
1674                                        appWidgetInfo.restoreStatus = restoreStatus;
1675                                        Integer installProgress = installingPkgs.get(component.getPackageName());
1676
1677                                        if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
1678                                            // Restore has started once.
1679                                        } else if (installProgress != null) {
1680                                            // App restore has started. Update the flag
1681                                            appWidgetInfo.restoreStatus |=
1682                                                    LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1683                                        } else if (!isSafeMode) {
1684                                            FileLog.d(TAG, "Unrestored widget removed: " + component);
1685                                            itemsToRemove.add(id);
1686                                            continue;
1687                                        }
1688
1689                                        appWidgetInfo.installProgress =
1690                                                installProgress == null ? 0 : installProgress;
1691                                    }
1692                                    if (appWidgetInfo.hasRestoreFlag(
1693                                            LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
1694                                        intentDescription = c.getString(intentIndex);
1695                                        if (!TextUtils.isEmpty(intentDescription)) {
1696                                            appWidgetInfo.bindOptions =
1697                                                    Intent.parseUri(intentDescription, 0);
1698                                        }
1699                                    }
1700
1701                                    appWidgetInfo.id = id;
1702                                    appWidgetInfo.screenId = c.getInt(screenIndex);
1703                                    appWidgetInfo.cellX = c.getInt(cellXIndex);
1704                                    appWidgetInfo.cellY = c.getInt(cellYIndex);
1705                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
1706                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
1707                                    appWidgetInfo.user = user;
1708
1709                                    if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1710                                        container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1711                                        Log.e(TAG, "Widget found where container != " +
1712                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1713                                        itemsToRemove.add(id);
1714                                        continue;
1715                                    }
1716
1717                                    appWidgetInfo.container = container;
1718                                    // check & update map of what's occupied
1719                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) {
1720                                        itemsToRemove.add(id);
1721                                        break;
1722                                    }
1723
1724                                    if (!customWidget) {
1725                                        String providerName =
1726                                                appWidgetInfo.providerName.flattenToString();
1727                                        if (!providerName.equals(savedProvider) ||
1728                                                (appWidgetInfo.restoreStatus != restoreStatus)) {
1729                                            ContentValues values = new ContentValues();
1730                                            values.put(
1731                                                    LauncherSettings.Favorites.APPWIDGET_PROVIDER,
1732                                                    providerName);
1733                                            values.put(LauncherSettings.Favorites.RESTORED,
1734                                                    appWidgetInfo.restoreStatus);
1735                                            updateItem(id, values);
1736                                        }
1737                                    }
1738                                    sBgDataModel.addItem(appWidgetInfo, false);
1739                                }
1740                                break;
1741                            }
1742                        } catch (Exception e) {
1743                            Log.e(TAG, "Desktop items loading interrupted", e);
1744                        }
1745                    }
1746                } finally {
1747                    Utilities.closeSilently(c);
1748                }
1749
1750                // Break early if we've stopped loading
1751                if (mStopped) {
1752                    sBgDataModel.clear();
1753                    return;
1754                }
1755
1756                if (itemsToRemove.size() > 0) {
1757                    // Remove dead items
1758                    contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
1759                            Utilities.createDbSelectionQuery(
1760                                    LauncherSettings.Favorites._ID, itemsToRemove), null);
1761                    if (DEBUG_LOADERS) {
1762                        Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
1763                                LauncherSettings.Favorites._ID, itemsToRemove));
1764                    }
1765
1766                    // Remove any empty folder
1767                    ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
1768                            .call(contentResolver,
1769                                    LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
1770                            .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
1771                    for (long folderId : deletedFolderIds) {
1772                        sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
1773                        sBgDataModel.folders.remove(folderId);
1774                        sBgDataModel.itemsIdMap.remove(folderId);
1775                    }
1776                }
1777
1778                // Unpin shortcuts that don't exist on the workspace.
1779                HashSet<ShortcutKey> pendingShortcuts =
1780                        InstallShortcutReceiver.getPendingShortcuts(context);
1781                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
1782                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
1783                    if ((numTimesPinned == null || numTimesPinned.value == 0)
1784                            && !pendingShortcuts.contains(key)) {
1785                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
1786                        shortcutManager.unpinShortcut(key);
1787                    }
1788                }
1789
1790                // Sort all the folder items and make sure the first 3 items are high resolution.
1791                for (FolderInfo folder : sBgDataModel.folders) {
1792                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
1793                    int pos = 0;
1794                    for (ShortcutInfo info : folder.contents) {
1795                        if (info.usingLowResIcon &&
1796                                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1797                            mIconCache.getTitleAndIcon(
1798                                    info, info.getPromisedIntent(), info.user, false);
1799                        }
1800                        pos ++;
1801                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
1802                            break;
1803                        }
1804                    }
1805                }
1806
1807                if (restoredRows.size() > 0) {
1808                    // Update restored items that no longer require special handling
1809                    ContentValues values = new ContentValues();
1810                    values.put(LauncherSettings.Favorites.RESTORED, 0);
1811                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
1812                            Utilities.createDbSelectionQuery(
1813                                    LauncherSettings.Favorites._ID, restoredRows), null);
1814                }
1815
1816                if (!isSdCardReady && !pendingPackages.isEmpty()) {
1817                    context.registerReceiver(
1818                            new SdCardAvailableReceiver(
1819                                    LauncherModel.this, mContext, pendingPackages),
1820                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
1821                            null,
1822                            sWorker);
1823                }
1824
1825                // Remove any empty screens
1826                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens);
1827                for (ItemInfo item: sBgDataModel.itemsIdMap) {
1828                    long screenId = item.screenId;
1829                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1830                            unusedScreens.contains(screenId)) {
1831                        unusedScreens.remove(screenId);
1832                    }
1833                }
1834
1835                // If there are any empty screens remove them, and update.
1836                if (unusedScreens.size() != 0) {
1837                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
1838                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
1839                }
1840
1841                if (DEBUG_LOADERS) {
1842                    Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
1843                    Log.d(TAG, "workspace layout: ");
1844                    int nScreens = occupied.size();
1845                    for (int y = 0; y < countY; y++) {
1846                        String line = "";
1847
1848                        for (int i = 0; i < nScreens; i++) {
1849                            long screenId = occupied.keyAt(i);
1850                            if (screenId > 0) {
1851                                line += " | ";
1852                            }
1853                        }
1854                        Log.d(TAG, "[ " + line + " ]");
1855                    }
1856                }
1857            }
1858            if (LauncherAppState.PROFILE_STARTUP) {
1859                Trace.endSection();
1860            }
1861        }
1862
1863        /**
1864         * Partially updates the item without any notification. Must be called on the worker thread.
1865         */
1866        private void updateItem(long itemId, ContentValues update) {
1867            mContext.getContentResolver().update(
1868                    LauncherSettings.Favorites.CONTENT_URI,
1869                    update,
1870                    BaseColumns._ID + "= ?",
1871                    new String[]{Long.toString(itemId)});
1872        }
1873
1874        /** Filters the set of items who are directly or indirectly (via another container) on the
1875         * specified screen. */
1876        private void filterCurrentWorkspaceItems(long currentScreenId,
1877                ArrayList<ItemInfo> allWorkspaceItems,
1878                ArrayList<ItemInfo> currentScreenItems,
1879                ArrayList<ItemInfo> otherScreenItems) {
1880            // Purge any null ItemInfos
1881            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
1882            while (iter.hasNext()) {
1883                ItemInfo i = iter.next();
1884                if (i == null) {
1885                    iter.remove();
1886                }
1887            }
1888
1889            // Order the set of items by their containers first, this allows use to walk through the
1890            // list sequentially, build up a list of containers that are in the specified screen,
1891            // as well as all items in those containers.
1892            Set<Long> itemsOnScreen = new HashSet<Long>();
1893            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
1894                @Override
1895                public int compare(ItemInfo lhs, ItemInfo rhs) {
1896                    return Utilities.longCompare(lhs.container, rhs.container);
1897                }
1898            });
1899            for (ItemInfo info : allWorkspaceItems) {
1900                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1901                    if (info.screenId == currentScreenId) {
1902                        currentScreenItems.add(info);
1903                        itemsOnScreen.add(info.id);
1904                    } else {
1905                        otherScreenItems.add(info);
1906                    }
1907                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1908                    currentScreenItems.add(info);
1909                    itemsOnScreen.add(info.id);
1910                } else {
1911                    if (itemsOnScreen.contains(info.container)) {
1912                        currentScreenItems.add(info);
1913                        itemsOnScreen.add(info.id);
1914                    } else {
1915                        otherScreenItems.add(info);
1916                    }
1917                }
1918            }
1919        }
1920
1921        /** Filters the set of widgets which are on the specified screen. */
1922        private void filterCurrentAppWidgets(long currentScreenId,
1923                ArrayList<LauncherAppWidgetInfo> appWidgets,
1924                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
1925                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
1926
1927            for (LauncherAppWidgetInfo widget : appWidgets) {
1928                if (widget == null) continue;
1929                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1930                        widget.screenId == currentScreenId) {
1931                    currentScreenWidgets.add(widget);
1932                } else {
1933                    otherScreenWidgets.add(widget);
1934                }
1935            }
1936        }
1937
1938        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
1939         * right) */
1940        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
1941            final LauncherAppState app = LauncherAppState.getInstance();
1942            final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1943            final int screenCols = profile.numColumns;
1944            final int screenCellCount = profile.numColumns * profile.numRows;
1945            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
1946                @Override
1947                public int compare(ItemInfo lhs, ItemInfo rhs) {
1948                    if (lhs.container == rhs.container) {
1949                        // Within containers, order by their spatial position in that container
1950                        switch ((int) lhs.container) {
1951                            case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
1952                                long lr = (lhs.screenId * screenCellCount +
1953                                        lhs.cellY * screenCols + lhs.cellX);
1954                                long rr = (rhs.screenId * screenCellCount +
1955                                        rhs.cellY * screenCols + rhs.cellX);
1956                                return Utilities.longCompare(lr, rr);
1957                            }
1958                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
1959                                // We currently use the screen id as the rank
1960                                return Utilities.longCompare(lhs.screenId, rhs.screenId);
1961                            }
1962                            default:
1963                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
1964                                    throw new RuntimeException("Unexpected container type when " +
1965                                            "sorting workspace items.");
1966                                }
1967                                return 0;
1968                        }
1969                    } else {
1970                        // Between containers, order by hotseat, desktop
1971                        return Utilities.longCompare(lhs.container, rhs.container);
1972                    }
1973                }
1974            });
1975        }
1976
1977        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
1978                final ArrayList<Long> orderedScreens) {
1979            final Runnable r = new Runnable() {
1980                @Override
1981                public void run() {
1982                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1983                    if (callbacks != null) {
1984                        callbacks.bindScreens(orderedScreens);
1985                    }
1986                }
1987            };
1988            runOnMainThread(r);
1989        }
1990
1991        private void bindWorkspaceItems(final Callbacks oldCallbacks,
1992                final ArrayList<ItemInfo> workspaceItems,
1993                final ArrayList<LauncherAppWidgetInfo> appWidgets,
1994                final Executor executor) {
1995
1996            // Bind the workspace items
1997            int N = workspaceItems.size();
1998            for (int i = 0; i < N; i += ITEMS_CHUNK) {
1999                final int start = i;
2000                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2001                final Runnable r = new Runnable() {
2002                    @Override
2003                    public void run() {
2004                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2005                        if (callbacks != null) {
2006                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
2007                                    false);
2008                        }
2009                    }
2010                };
2011                executor.execute(r);
2012            }
2013
2014            // Bind the widgets, one at a time
2015            N = appWidgets.size();
2016            for (int i = 0; i < N; i++) {
2017                final LauncherAppWidgetInfo widget = appWidgets.get(i);
2018                final Runnable r = new Runnable() {
2019                    public void run() {
2020                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2021                        if (callbacks != null) {
2022                            callbacks.bindAppWidget(widget);
2023                        }
2024                    }
2025                };
2026                executor.execute(r);
2027            }
2028        }
2029
2030        /**
2031         * Binds all loaded data to actual views on the main thread.
2032         */
2033        private void bindWorkspace(int synchronizeBindPage) {
2034            final long t = SystemClock.uptimeMillis();
2035            Runnable r;
2036
2037            // Don't use these two variables in any of the callback runnables.
2038            // Otherwise we hold a reference to them.
2039            final Callbacks oldCallbacks = mCallbacks.get();
2040            if (oldCallbacks == null) {
2041                // This launcher has exited and nobody bothered to tell us.  Just bail.
2042                Log.w(TAG, "LoaderTask running with no launcher");
2043                return;
2044            }
2045
2046            // Save a copy of all the bg-thread collections
2047            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
2048            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
2049            ArrayList<Long> orderedScreenIds = new ArrayList<>();
2050
2051            synchronized (sBgDataModel) {
2052                workspaceItems.addAll(sBgDataModel.workspaceItems);
2053                appWidgets.addAll(sBgDataModel.appWidgets);
2054                orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
2055            }
2056
2057            final int currentScreen;
2058            {
2059                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
2060                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
2061                if (currScreen >= orderedScreenIds.size()) {
2062                    // There may be no workspace screens (just hotseat items and an empty page).
2063                    currScreen = PagedView.INVALID_RESTORE_PAGE;
2064                }
2065                currentScreen = currScreen;
2066            }
2067            final boolean validFirstPage = currentScreen >= 0;
2068            final long currentScreenId =
2069                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
2070
2071            // Separate the items that are on the current screen, and all the other remaining items
2072            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
2073            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
2074            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
2075            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
2076
2077            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2078                    otherWorkspaceItems);
2079            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2080                    otherAppWidgets);
2081            sortWorkspaceItemsSpatially(currentWorkspaceItems);
2082            sortWorkspaceItemsSpatially(otherWorkspaceItems);
2083
2084            // Tell the workspace that we're about to start binding items
2085            r = new Runnable() {
2086                public void run() {
2087                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2088                    if (callbacks != null) {
2089                        callbacks.clearPendingBinds();
2090                        callbacks.startBinding();
2091                    }
2092                }
2093            };
2094            runOnMainThread(r);
2095
2096            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2097
2098            Executor mainExecutor = new DeferredMainThreadExecutor();
2099            // Load items on the current page.
2100            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
2101
2102            // In case of validFirstPage, only bind the first screen, and defer binding the
2103            // remaining screens after first onDraw (and an optional the fade animation whichever
2104            // happens later).
2105            // This ensures that the first screen is immediately visible (eg. during rotation)
2106            // In case of !validFirstPage, bind all pages one after other.
2107            final Executor deferredExecutor =
2108                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
2109
2110            mainExecutor.execute(new Runnable() {
2111                @Override
2112                public void run() {
2113                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2114                    if (callbacks != null) {
2115                        callbacks.finishFirstPageBind(
2116                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
2117                    }
2118                }
2119            });
2120
2121            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
2122
2123            // Tell the workspace that we're done binding items
2124            r = new Runnable() {
2125                public void run() {
2126                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2127                    if (callbacks != null) {
2128                        callbacks.finishBindingItems();
2129                    }
2130
2131                    mIsLoadingAndBindingWorkspace = false;
2132
2133                    // Run all the bind complete runnables after workspace is bound.
2134                    if (!mBindCompleteRunnables.isEmpty()) {
2135                        synchronized (mBindCompleteRunnables) {
2136                            for (final Runnable r : mBindCompleteRunnables) {
2137                                runOnWorkerThread(r);
2138                            }
2139                            mBindCompleteRunnables.clear();
2140                        }
2141                    }
2142
2143                    // If we're profiling, ensure this is the last thing in the queue.
2144                    if (DEBUG_LOADERS) {
2145                        Log.d(TAG, "bound workspace in "
2146                            + (SystemClock.uptimeMillis()-t) + "ms");
2147                    }
2148
2149                }
2150            };
2151            deferredExecutor.execute(r);
2152
2153            if (validFirstPage) {
2154                r = new Runnable() {
2155                    public void run() {
2156                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2157                        if (callbacks != null) {
2158                            // We are loading synchronously, which means, some of the pages will be
2159                            // bound after first draw. Inform the callbacks that page binding is
2160                            // not complete, and schedule the remaining pages.
2161                            if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2162                                callbacks.onPageBoundSynchronously(currentScreen);
2163                            }
2164                            callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
2165                        }
2166                    }
2167                };
2168                runOnMainThread(r);
2169            }
2170        }
2171
2172        private void loadAndBindAllApps() {
2173            if (DEBUG_LOADERS) {
2174                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2175            }
2176            if (!mAllAppsLoaded) {
2177                loadAllApps();
2178                synchronized (LoaderTask.this) {
2179                    if (mStopped) {
2180                        return;
2181                    }
2182                }
2183                updateIconCache();
2184                synchronized (LoaderTask.this) {
2185                    if (mStopped) {
2186                        return;
2187                    }
2188                    mAllAppsLoaded = true;
2189                }
2190            } else {
2191                onlyBindAllApps();
2192            }
2193        }
2194
2195        private void updateIconCache() {
2196            // Ignore packages which have a promise icon.
2197            HashSet<String> packagesToIgnore = new HashSet<>();
2198            synchronized (sBgDataModel) {
2199                for (ItemInfo info : sBgDataModel.itemsIdMap) {
2200                    if (info instanceof ShortcutInfo) {
2201                        ShortcutInfo si = (ShortcutInfo) info;
2202                        if (si.isPromise() && si.getTargetComponent() != null) {
2203                            packagesToIgnore.add(si.getTargetComponent().getPackageName());
2204                        }
2205                    } else if (info instanceof LauncherAppWidgetInfo) {
2206                        LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2207                        if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2208                            packagesToIgnore.add(lawi.providerName.getPackageName());
2209                        }
2210                    }
2211                }
2212            }
2213            mIconCache.updateDbIcons(packagesToIgnore);
2214        }
2215
2216        private void onlyBindAllApps() {
2217            final Callbacks oldCallbacks = mCallbacks.get();
2218            if (oldCallbacks == null) {
2219                // This launcher has exited and nobody bothered to tell us.  Just bail.
2220                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2221                return;
2222            }
2223
2224            // shallow copy
2225            @SuppressWarnings("unchecked")
2226            final ArrayList<AppInfo> list
2227                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2228            Runnable r = new Runnable() {
2229                public void run() {
2230                    final long t = SystemClock.uptimeMillis();
2231                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2232                    if (callbacks != null) {
2233                        callbacks.bindAllApplications(list);
2234                    }
2235                    if (DEBUG_LOADERS) {
2236                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2237                                + (SystemClock.uptimeMillis() - t) + "ms");
2238                    }
2239                }
2240            };
2241            runOnMainThread(r);
2242        }
2243
2244        private void loadAllApps() {
2245            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2246
2247            final Callbacks oldCallbacks = mCallbacks.get();
2248            if (oldCallbacks == null) {
2249                // This launcher has exited and nobody bothered to tell us.  Just bail.
2250                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2251                return;
2252            }
2253
2254            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2255
2256            // Clear the list of apps
2257            mBgAllAppsList.clear();
2258            for (UserHandleCompat user : profiles) {
2259                // Query for the set of apps
2260                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2261                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2262                if (DEBUG_LOADERS) {
2263                    Log.d(TAG, "getActivityList took "
2264                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2265                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2266                }
2267                // Fail if we don't have any apps
2268                // TODO: Fix this. Only fail for the current user.
2269                if (apps == null || apps.isEmpty()) {
2270                    return;
2271                }
2272                boolean quietMode = mUserManager.isQuietModeEnabled(user);
2273                // Create the ApplicationInfos
2274                for (int i = 0; i < apps.size(); i++) {
2275                    LauncherActivityInfoCompat app = apps.get(i);
2276                    // This builds the icon bitmaps.
2277                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
2278                }
2279
2280                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2281                if (heuristic != null) {
2282                    final Runnable r = new Runnable() {
2283
2284                        @Override
2285                        public void run() {
2286                            heuristic.processUserApps(apps);
2287                        }
2288                    };
2289                    runOnMainThread(new Runnable() {
2290
2291                        @Override
2292                        public void run() {
2293                            // Check isLoadingWorkspace on the UI thread, as it is updated on
2294                            // the UI thread.
2295                            if (mIsLoadingAndBindingWorkspace) {
2296                                synchronized (mBindCompleteRunnables) {
2297                                    mBindCompleteRunnables.add(r);
2298                                }
2299                            } else {
2300                                runOnWorkerThread(r);
2301                            }
2302                        }
2303                    });
2304                }
2305            }
2306            // Huh? Shouldn't this be inside the Runnable below?
2307            final ArrayList<AppInfo> added = mBgAllAppsList.added;
2308            mBgAllAppsList.added = new ArrayList<AppInfo>();
2309
2310            // Post callback on main thread
2311            mHandler.post(new Runnable() {
2312                public void run() {
2313
2314                    final long bindTime = SystemClock.uptimeMillis();
2315                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2316                    if (callbacks != null) {
2317                        callbacks.bindAllApplications(added);
2318                        if (DEBUG_LOADERS) {
2319                            Log.d(TAG, "bound " + added.size() + " apps in "
2320                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
2321                        }
2322                    } else {
2323                        Log.i(TAG, "not binding apps: no Launcher activity");
2324                    }
2325                }
2326            });
2327            // Cleanup any data stored for a deleted user.
2328            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2329            if (DEBUG_LOADERS) {
2330                Log.d(TAG, "Icons processed in "
2331                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
2332            }
2333        }
2334
2335        private void loadAndBindDeepShortcuts() {
2336            if (DEBUG_LOADERS) {
2337                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
2338            }
2339            if (!mDeepShortcutsLoaded) {
2340                sBgDataModel.deepShortcutMap.clear();
2341                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
2342                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
2343                if (mHasShortcutHostPermission) {
2344                    for (UserHandleCompat user : mUserManager.getUserProfiles()) {
2345                        if (mUserManager.isUserUnlocked(user)) {
2346                            List<ShortcutInfoCompat> shortcuts =
2347                                    shortcutManager.queryForAllShortcuts(user);
2348                            sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
2349                        }
2350                    }
2351                }
2352                synchronized (LoaderTask.this) {
2353                    if (mStopped) {
2354                        return;
2355                    }
2356                    mDeepShortcutsLoaded = true;
2357                }
2358            }
2359            bindDeepShortcuts();
2360        }
2361
2362        public void dumpState() {
2363            synchronized (sBgDataModel) {
2364                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2365                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2366                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2367                Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
2368            }
2369        }
2370    }
2371
2372    public void bindDeepShortcuts() {
2373        final MultiHashMap<ComponentKey, String> shortcutMapCopy =
2374                sBgDataModel.deepShortcutMap.clone();
2375        Runnable r = new Runnable() {
2376            @Override
2377            public void run() {
2378                Callbacks callbacks = getCallback();
2379                if (callbacks != null) {
2380                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
2381                }
2382            }
2383        };
2384        runOnMainThread(r);
2385    }
2386
2387    /**
2388     * Refreshes the cached shortcuts if the shortcut permission has changed.
2389     * Current implementation simply reloads the workspace, but it can be optimized to
2390     * use partial updates similar to {@link UserManagerCompat}
2391     */
2392    public void refreshShortcutsIfRequired() {
2393        if (Utilities.ATLEAST_NOUGAT_MR1) {
2394            sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
2395            sWorker.post(mShortcutPermissionCheckRunnable);
2396        }
2397    }
2398
2399    /**
2400     * Called when the icons for packages have been updated in the icon cache.
2401     */
2402    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2403        // If any package icon has changed (app was updated while launcher was dead),
2404        // update the corresponding shortcuts.
2405        enqueueModelUpdateTask(new CacheDataUpdatedTask(
2406                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
2407    }
2408
2409    void enqueueModelUpdateTask(BaseModelUpdateTask task) {
2410        task.init(this);
2411        runOnWorkerThread(task);
2412    }
2413
2414    /**
2415     * A task to be executed on the current callbacks on the UI thread.
2416     * If there is no current callbacks, the task is ignored.
2417     */
2418    public interface CallbackTask {
2419
2420        void execute(Callbacks callbacks);
2421    }
2422
2423    /**
2424     * A runnable which changes/updates the data model of the launcher based on certain events.
2425     */
2426    public static abstract class BaseModelUpdateTask implements Runnable {
2427
2428        private LauncherModel mModel;
2429        private DeferredHandler mUiHandler;
2430
2431        /* package private */
2432        void init(LauncherModel model) {
2433            mModel = model;
2434            mUiHandler = mModel.mHandler;
2435        }
2436
2437        @Override
2438        public void run() {
2439            if (!mModel.mHasLoaderCompletedOnce) {
2440                // Loader has not yet run.
2441                return;
2442            }
2443            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
2444        }
2445
2446        /**
2447         * Execute the actual task. Called on the worker thread.
2448         */
2449        public abstract void execute(
2450                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
2451
2452        /**
2453         * Schedules a {@param task} to be executed on the current callbacks.
2454         */
2455        public final void scheduleCallbackTask(final CallbackTask task) {
2456            final Callbacks callbacks = mModel.getCallback();
2457            mUiHandler.post(new Runnable() {
2458                public void run() {
2459                    Callbacks cb = mModel.getCallback();
2460                    if (callbacks == cb && cb != null) {
2461                        task.execute(callbacks);
2462                    }
2463                }
2464            });
2465        }
2466    }
2467
2468    /**
2469     * Repopulates the shortcut info, possibly updating any icon already on the workspace.
2470     */
2471    public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
2472        enqueueModelUpdateTask(new ExtendedModelTask() {
2473            @Override
2474            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
2475                info.updateFromDeepShortcutInfo(fullDetail, app.getContext());
2476
2477                ArrayList<ShortcutInfo> update = new ArrayList<>();
2478                update.add(info);
2479                bindUpdatedShortcuts(update, fullDetail.getUserHandle());
2480            }
2481        });
2482    }
2483
2484    private void bindWidgetsModel(final Callbacks callbacks) {
2485        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
2486                = mBgWidgetsModel.getWidgetsMap().clone();
2487        mHandler.post(new Runnable() {
2488            @Override
2489            public void run() {
2490                Callbacks cb = getCallback();
2491                if (callbacks == cb && cb != null) {
2492                    callbacks.bindAllWidgets(widgets);
2493                }
2494            }
2495        });
2496    }
2497
2498    public void refreshAndBindWidgetsAndShortcuts(
2499            final Callbacks callbacks, final boolean bindFirst) {
2500        runOnWorkerThread(new Runnable() {
2501            @Override
2502            public void run() {
2503                if (bindFirst && !mBgWidgetsModel.isEmpty()) {
2504                    bindWidgetsModel(callbacks);
2505                }
2506                ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext());
2507                bindWidgetsModel(callbacks);
2508
2509                // update the Widget entries inside DB on the worker thread.
2510                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(allWidgets);
2511            }
2512        });
2513    }
2514
2515    /**
2516     * Make an ShortcutInfo object for a restored application or shortcut item that points
2517     * to a package that is not yet installed on the system.
2518     */
2519    public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
2520            int promiseType, int itemType, CursorIconInfo iconInfo) {
2521        final ShortcutInfo info = new ShortcutInfo();
2522        info.user = UserHandleCompat.myUserHandle();
2523        info.iconBitmap = iconInfo.loadIcon(c, info);
2524        // the fallback icon
2525        if (info.iconBitmap == null) {
2526            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
2527        }
2528
2529        if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
2530            String title = iconInfo.getTitle(c);
2531            if (!TextUtils.isEmpty(title)) {
2532                info.title = Utilities.trim(title);
2533            }
2534        } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
2535            if (TextUtils.isEmpty(info.title)) {
2536                info.title = iconInfo.getTitle(c);
2537            }
2538        } else {
2539            throw new InvalidParameterException("Invalid restoreType " + promiseType);
2540        }
2541
2542        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
2543        info.itemType = itemType;
2544        info.promisedIntent = intent;
2545        info.status = promiseType;
2546        return info;
2547    }
2548
2549    /**
2550     * Make an Intent object for a restored application or shortcut item that points
2551     * to the market page for the item.
2552     */
2553    @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
2554        ComponentName componentName = intent.getComponent();
2555        return getMarketIntent(componentName.getPackageName());
2556    }
2557
2558    static Intent getMarketIntent(String packageName) {
2559        return new Intent(Intent.ACTION_VIEW)
2560            .setData(new Uri.Builder()
2561                .scheme("market")
2562                .authority("details")
2563                .appendQueryParameter("id", packageName)
2564                .build());
2565    }
2566
2567    /**
2568     * Make an ShortcutInfo object for a shortcut that is an application.
2569     *
2570     * If c is not null, then it will be used to fill in missing data like the title and icon.
2571     */
2572    public ShortcutInfo getAppShortcutInfo(Intent intent,
2573            UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
2574            boolean allowMissingTarget, boolean useLowResIcon) {
2575        if (user == null) {
2576            Log.d(TAG, "Null user found in getShortcutInfo");
2577            return null;
2578        }
2579
2580        ComponentName componentName = intent.getComponent();
2581        if (componentName == null) {
2582            Log.d(TAG, "Missing component found in getShortcutInfo");
2583            return null;
2584        }
2585
2586        Intent newIntent = new Intent(intent.getAction(), null);
2587        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2588        newIntent.setComponent(componentName);
2589        LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
2590        if ((lai == null) && !allowMissingTarget) {
2591            Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
2592            return null;
2593        }
2594
2595        final ShortcutInfo info = new ShortcutInfo();
2596        mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
2597        if (mIconCache.isDefaultIcon(info.iconBitmap, user) && c != null) {
2598            Bitmap icon = iconInfo.loadIcon(c);
2599            info.iconBitmap = icon != null ? icon : mIconCache.getDefaultIcon(user);
2600        }
2601
2602        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
2603            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
2604        }
2605
2606        // from the db
2607        if (TextUtils.isEmpty(info.title) && c != null) {
2608            info.title = iconInfo.getTitle(c);
2609        }
2610
2611        // fall back to the class name of the activity
2612        if (info.title == null) {
2613            info.title = componentName.getClassName();
2614        }
2615
2616        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
2617        info.user = user;
2618        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
2619        return info;
2620    }
2621
2622    /**
2623     * Make an ShortcutInfo object for a shortcut that isn't an application.
2624     */
2625    @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
2626        final ShortcutInfo info = new ShortcutInfo();
2627        // Non-app shortcuts are only supported for current user.
2628        info.user = UserHandleCompat.myUserHandle();
2629        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
2630
2631        // TODO: If there's an explicit component and we can't install that, delete it.
2632
2633        loadInfoFromCursor(info, c, iconInfo);
2634        return info;
2635    }
2636
2637    /**
2638     * Make an ShortcutInfo object for a shortcut that isn't an application.
2639     */
2640    public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
2641        info.title = iconInfo.getTitle(c);
2642        info.iconBitmap = iconInfo.loadIcon(c, info);
2643        // the fallback icon
2644        if (info.iconBitmap == null) {
2645            info.iconBitmap = mIconCache.getDefaultIcon(info.user);
2646        }
2647    }
2648
2649    ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
2650        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
2651        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
2652        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
2653
2654        if (intent == null) {
2655            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
2656            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
2657            return null;
2658        }
2659
2660        final ShortcutInfo info = new ShortcutInfo();
2661
2662        // Only support intents for current user for now. Intents sent from other
2663        // users wouldn't get here without intent forwarding anyway.
2664        info.user = UserHandleCompat.myUserHandle();
2665
2666        if (bitmap instanceof Bitmap) {
2667            info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, context);
2668        } else {
2669            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
2670            if (extra instanceof ShortcutIconResource) {
2671                info.iconResource = (ShortcutIconResource) extra;
2672                info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, context);
2673            }
2674        }
2675        if (info.iconBitmap == null) {
2676            info.iconBitmap = mIconCache.getDefaultIcon(info.user);
2677        }
2678
2679        info.title = Utilities.trim(name);
2680        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
2681        info.intent = intent;
2682
2683        return info;
2684    }
2685
2686    static boolean isValidProvider(AppWidgetProviderInfo provider) {
2687        return (provider != null) && (provider.provider != null)
2688                && (provider.provider.getPackageName() != null);
2689    }
2690
2691    public void dumpState() {
2692        Log.d(TAG, "mCallbacks=" + mCallbacks);
2693        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
2694        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
2695        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
2696        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
2697        if (mLoaderTask != null) {
2698            mLoaderTask.dumpState();
2699        } else {
2700            Log.d(TAG, "mLoaderTask=null");
2701        }
2702    }
2703
2704    public Callbacks getCallback() {
2705        return mCallbacks != null ? mCallbacks.get() : null;
2706    }
2707
2708    /**
2709     * @return {@link FolderInfo} if its already loaded.
2710     */
2711    public FolderInfo findFolderById(Long folderId) {
2712        synchronized (sBgDataModel) {
2713            return sBgDataModel.folders.get(folderId);
2714        }
2715    }
2716
2717    @Thunk class DeferredMainThreadExecutor implements Executor {
2718
2719        @Override
2720        public void execute(Runnable command) {
2721            runOnMainThread(command);
2722        }
2723    }
2724
2725    /**
2726     * @return the looper for the worker thread which can be used to start background tasks.
2727     */
2728    public static Looper getWorkerLooper() {
2729        return sWorkerThread.getLooper();
2730    }
2731}
2732