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