LauncherModel.java revision 81e44914500c7cc6204d25390f5999fa53559044
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.IntentFilter;
28import android.content.pm.LauncherActivityInfo;
29import android.content.pm.PackageManager;
30import android.net.Uri;
31import android.os.Handler;
32import android.os.HandlerThread;
33import android.os.Looper;
34import android.os.Process;
35import android.os.SystemClock;
36import android.os.Trace;
37import android.os.UserHandle;
38import android.text.TextUtils;
39import android.util.Log;
40import android.util.LongSparseArray;
41import android.util.MutableInt;
42
43import com.android.launcher3.compat.AppWidgetManagerCompat;
44import com.android.launcher3.compat.LauncherAppsCompat;
45import com.android.launcher3.compat.PackageInstallerCompat;
46import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
47import com.android.launcher3.compat.UserManagerCompat;
48import com.android.launcher3.config.ProviderConfig;
49import com.android.launcher3.dynamicui.ExtractionUtils;
50import com.android.launcher3.folder.Folder;
51import com.android.launcher3.folder.FolderIcon;
52import com.android.launcher3.graphics.LauncherIcons;
53import com.android.launcher3.logging.FileLog;
54import com.android.launcher3.model.AddWorkspaceItemsTask;
55import com.android.launcher3.model.BgDataModel;
56import com.android.launcher3.model.CacheDataUpdatedTask;
57import com.android.launcher3.model.ExtendedModelTask;
58import com.android.launcher3.model.GridSizeMigrationTask;
59import com.android.launcher3.model.LoaderCursor;
60import com.android.launcher3.model.PackageInstallStateChangedTask;
61import com.android.launcher3.model.PackageItemInfo;
62import com.android.launcher3.model.PackageUpdatedTask;
63import com.android.launcher3.model.SdCardAvailableReceiver;
64import com.android.launcher3.model.ShortcutsChangedTask;
65import com.android.launcher3.model.UserLockStateChangedTask;
66import com.android.launcher3.model.WidgetItem;
67import com.android.launcher3.model.WidgetsModel;
68import com.android.launcher3.provider.ImportDataTask;
69import com.android.launcher3.provider.LauncherDbUtils;
70import com.android.launcher3.shortcuts.DeepShortcutManager;
71import com.android.launcher3.shortcuts.ShortcutInfoCompat;
72import com.android.launcher3.shortcuts.ShortcutKey;
73import com.android.launcher3.util.ComponentKey;
74import com.android.launcher3.util.ContentWriter;
75import com.android.launcher3.util.ItemInfoMatcher;
76import com.android.launcher3.util.ManagedProfileHeuristic;
77import com.android.launcher3.util.MultiHashMap;
78import com.android.launcher3.util.PackageManagerHelper;
79import com.android.launcher3.util.Preconditions;
80import com.android.launcher3.util.Provider;
81import com.android.launcher3.util.Thunk;
82import com.android.launcher3.util.ViewOnDrawExecutor;
83
84import java.lang.ref.WeakReference;
85import java.net.URISyntaxException;
86import java.util.ArrayList;
87import java.util.Collections;
88import java.util.Comparator;
89import java.util.HashMap;
90import java.util.HashSet;
91import java.util.Iterator;
92import java.util.List;
93import java.util.Map;
94import java.util.Set;
95import java.util.concurrent.Executor;
96
97/**
98 * Maintains in-memory state of the Launcher. It is expected that there should be only one
99 * LauncherModel object held in a static. Also provide APIs for updating the database state
100 * for the Launcher.
101 */
102public class LauncherModel extends BroadcastReceiver
103        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
104    static final boolean DEBUG_LOADERS = false;
105    private static final boolean DEBUG_RECEIVER = false;
106
107    static final String TAG = "Launcher.Model";
108
109    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
110    private static final long INVALID_SCREEN_ID = -1L;
111
112    @Thunk final LauncherAppState mApp;
113    @Thunk final Object mLock = new Object();
114    @Thunk DeferredHandler mHandler = new DeferredHandler();
115    @Thunk LoaderTask mLoaderTask;
116    @Thunk boolean mIsLoaderTaskRunning;
117    @Thunk boolean mHasLoaderCompletedOnce;
118
119    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
120    static {
121        sWorkerThread.start();
122    }
123    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
124
125    // We start off with everything not loaded.  After that, we assume that
126    // our monitoring of the package manager provides all updates and we never
127    // need to do a requery.  These are only ever touched from the loader thread.
128    private boolean mWorkspaceLoaded;
129    private boolean mAllAppsLoaded;
130    private boolean mDeepShortcutsLoaded;
131
132    /**
133     * Set of runnables to be called on the background thread after the workspace binding
134     * is complete.
135     */
136    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
137
138    @Thunk WeakReference<Callbacks> mCallbacks;
139
140    // < only access in worker thread >
141    private final AllAppsList mBgAllAppsList;
142    // Entire list of widgets.
143    private final WidgetsModel mBgWidgetsModel;
144
145    private boolean mHasShortcutHostPermission;
146    // Runnable to check if the shortcuts permission has changed.
147    private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
148        @Override
149        public void run() {
150            if (mDeepShortcutsLoaded) {
151                boolean hasShortcutHostPermission =
152                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
153                if (hasShortcutHostPermission != mHasShortcutHostPermission) {
154                    mApp.reloadWorkspace();
155                }
156            }
157        }
158    };
159
160    /**
161     * All the static data should be accessed on the background thread, A lock should be acquired
162     * on this object when accessing any data from this model.
163     */
164    static final BgDataModel sBgDataModel = new BgDataModel();
165
166    // </ only access in worker thread >
167
168    private final IconCache mIconCache;
169
170    private final LauncherAppsCompat mLauncherApps;
171    private final UserManagerCompat mUserManager;
172
173    public interface Callbacks {
174        public boolean setLoadOnResume();
175        public int getCurrentWorkspaceScreen();
176        public void clearPendingBinds();
177        public void startBinding();
178        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
179                              boolean forceAnimateIcons);
180        public void bindScreens(ArrayList<Long> orderedScreenIds);
181        public void finishFirstPageBind(ViewOnDrawExecutor executor);
182        public void finishBindingItems();
183        public void bindAppWidget(LauncherAppWidgetInfo info);
184        public void bindAllApplications(ArrayList<AppInfo> apps);
185        public void bindAppsAdded(ArrayList<Long> newScreens,
186                                  ArrayList<ItemInfo> addNotAnimated,
187                                  ArrayList<ItemInfo> addAnimated,
188                                  ArrayList<AppInfo> addedApps);
189        public void bindAppsUpdated(ArrayList<AppInfo> apps);
190        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
191                ArrayList<ShortcutInfo> removed, UserHandle user);
192        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
193        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
194        public void bindWorkspaceComponentsRemoved(
195                HashSet<String> packageNames, HashSet<ComponentName> components,
196                UserHandle user);
197        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
198        public void notifyWidgetProvidersChanged();
199        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
200        public void onPageBoundSynchronously(int page);
201        public void executeOnNextDraw(ViewOnDrawExecutor executor);
202        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
203    }
204
205    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
206        Context context = app.getContext();
207        mApp = app;
208        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
209        mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
210        mIconCache = iconCache;
211
212        mLauncherApps = LauncherAppsCompat.getInstance(context);
213        mUserManager = UserManagerCompat.getInstance(context);
214    }
215
216    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
217     * posted on the main thread handler. */
218    private void runOnMainThread(Runnable r) {
219        if (sWorkerThread.getThreadId() == Process.myTid()) {
220            // If we are on the worker thread, post onto the main handler
221            mHandler.post(r);
222        } else {
223            r.run();
224        }
225    }
226
227    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
228     * posted on the worker thread handler. */
229    private static void runOnWorkerThread(Runnable r) {
230        if (sWorkerThread.getThreadId() == Process.myTid()) {
231            r.run();
232        } else {
233            // If we are not on the worker thread, then post to the worker handler
234            sWorker.post(r);
235        }
236    }
237
238    public void setPackageState(PackageInstallInfo installInfo) {
239        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
240    }
241
242    /**
243     * Updates the icons and label of all pending icons for the provided package name.
244     */
245    public void updateSessionDisplayInfo(final String packageName) {
246        HashSet<String> packages = new HashSet<>();
247        packages.add(packageName);
248        enqueueModelUpdateTask(new CacheDataUpdatedTask(
249                CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
250    }
251
252    /**
253     * Adds the provided items to the workspace.
254     */
255    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
256        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
257    }
258
259    /**
260     * Adds the provided items to the workspace.
261     */
262    public void addAndBindAddedWorkspaceItems(
263            Provider<List<ItemInfo>> appsProvider) {
264        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
265    }
266
267    /**
268     * Adds an item to the DB if it was not created previously, or move it to a new
269     * <container, screen, cellX, cellY>
270     */
271    public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
272            long screenId, int cellX, int cellY) {
273        if (item.container == ItemInfo.NO_ID) {
274            // From all apps
275            addItemToDatabase(context, item, container, screenId, cellX, cellY);
276        } else {
277            // From somewhere else
278            moveItemInDatabase(context, item, container, screenId, cellX, cellY);
279        }
280    }
281
282    static void checkItemInfoLocked(
283            final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
284        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
285        if (modelItem != null && item != modelItem) {
286            // check all the data is consistent
287            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
288                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
289                ShortcutInfo shortcut = (ShortcutInfo) item;
290                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
291                        modelShortcut.intent.filterEquals(shortcut.intent) &&
292                        modelShortcut.id == shortcut.id &&
293                        modelShortcut.itemType == shortcut.itemType &&
294                        modelShortcut.container == shortcut.container &&
295                        modelShortcut.screenId == shortcut.screenId &&
296                        modelShortcut.cellX == shortcut.cellX &&
297                        modelShortcut.cellY == shortcut.cellY &&
298                        modelShortcut.spanX == shortcut.spanX &&
299                        modelShortcut.spanY == shortcut.spanY) {
300                    // For all intents and purposes, this is the same object
301                    return;
302                }
303            }
304
305            // the modelItem needs to match up perfectly with item if our model is
306            // to be consistent with the database-- for now, just require
307            // modelItem == item or the equality check above
308            String msg = "item: " + ((item != null) ? item.toString() : "null") +
309                    "modelItem: " +
310                    ((modelItem != null) ? modelItem.toString() : "null") +
311                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
312            RuntimeException e = new RuntimeException(msg);
313            if (stackTrace != null) {
314                e.setStackTrace(stackTrace);
315            }
316            throw e;
317        }
318    }
319
320    static void checkItemInfo(final ItemInfo item) {
321        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
322        final long itemId = item.id;
323        Runnable r = new Runnable() {
324            public void run() {
325                synchronized (sBgDataModel) {
326                    checkItemInfoLocked(itemId, item, stackTrace);
327                }
328            }
329        };
330        runOnWorkerThread(r);
331    }
332
333    static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
334            final ItemInfo item, final String callingFunction) {
335        final long itemId = item.id;
336        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
337        final ContentResolver cr = context.getContentResolver();
338
339        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
340        final Context appContext = context.getApplicationContext();
341        Runnable r = new Runnable() {
342            public void run() {
343                cr.update(uri, writer.getValues(appContext), 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        final Context appContext = context.getApplicationContext();
559        Runnable r = new Runnable() {
560            public void run() {
561                cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(appContext));
562
563                synchronized (sBgDataModel) {
564                    checkItemInfoLocked(item.id, item, stackTrace);
565                    sBgDataModel.addItem(appContext, item, true);
566                }
567            }
568        };
569        runOnWorkerThread(r);
570    }
571
572    /**
573     * Removes the specified item from the database
574     */
575    public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
576        ArrayList<ItemInfo> items = new ArrayList<>();
577        items.add(item);
578        deleteItemsFromDatabase(context, items);
579    }
580
581    /**
582     * Removes all the items from the database matching {@param matcher}.
583     */
584    public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) {
585        deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap));
586    }
587
588    /**
589     * Removes the specified items from the database
590     */
591    public static void deleteItemsFromDatabase(Context context,
592            final Iterable<? extends ItemInfo> items) {
593        final ContentResolver cr = context.getContentResolver();
594        final Context appContext = context.getApplicationContext();
595        Runnable r = new Runnable() {
596            public void run() {
597                for (ItemInfo item : items) {
598                    final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
599                    cr.delete(uri, null, null);
600
601                    sBgDataModel.removeItem(appContext, item);
602                }
603            }
604        };
605        runOnWorkerThread(r);
606    }
607
608    /**
609     * Update the order of the workspace screens in the database. The array list contains
610     * a list of screen ids in the order that they should appear.
611     */
612    public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
613        final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
614        final ContentResolver cr = context.getContentResolver();
615        final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
616
617        // Remove any negative screen ids -- these aren't persisted
618        Iterator<Long> iter = screensCopy.iterator();
619        while (iter.hasNext()) {
620            long id = iter.next();
621            if (id < 0) {
622                iter.remove();
623            }
624        }
625
626        Runnable r = new Runnable() {
627            @Override
628            public void run() {
629                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
630                // Clear the table
631                ops.add(ContentProviderOperation.newDelete(uri).build());
632                int count = screensCopy.size();
633                for (int i = 0; i < count; i++) {
634                    ContentValues v = new ContentValues();
635                    long screenId = screensCopy.get(i);
636                    v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
637                    v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
638                    ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
639                }
640
641                try {
642                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
643                } catch (Exception ex) {
644                    throw new RuntimeException(ex);
645                }
646
647                synchronized (sBgDataModel) {
648                    sBgDataModel.workspaceScreens.clear();
649                    sBgDataModel.workspaceScreens.addAll(screensCopy);
650                }
651            }
652        };
653        runOnWorkerThread(r);
654    }
655
656    /**
657     * Remove the specified folder and all its contents from the database.
658     */
659    public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
660        final ContentResolver cr = context.getContentResolver();
661        final Context appContext = context.getApplicationContext();
662
663        Runnable r = new Runnable() {
664            public void run() {
665                cr.delete(LauncherSettings.Favorites.CONTENT_URI,
666                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
667                sBgDataModel.removeItem(appContext, info.contents);
668                info.contents.clear();
669
670                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
671                sBgDataModel.removeItem(appContext, info);
672            }
673        };
674        runOnWorkerThread(r);
675    }
676
677    /**
678     * Set this as the current Launcher activity object for the loader.
679     */
680    public void initialize(Callbacks callbacks) {
681        synchronized (mLock) {
682            Preconditions.assertUIThread();
683            // Remove any queued UI runnables
684            mHandler.cancelAll();
685            mCallbacks = new WeakReference<>(callbacks);
686        }
687    }
688
689    @Override
690    public void onPackageChanged(String packageName, UserHandle user) {
691        int op = PackageUpdatedTask.OP_UPDATE;
692        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
693    }
694
695    @Override
696    public void onPackageRemoved(String packageName, UserHandle user) {
697        onPackagesRemoved(user, packageName);
698    }
699
700    public void onPackagesRemoved(UserHandle user, String... packages) {
701        int op = PackageUpdatedTask.OP_REMOVE;
702        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
703    }
704
705    @Override
706    public void onPackageAdded(String packageName, UserHandle user) {
707        int op = PackageUpdatedTask.OP_ADD;
708        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
709    }
710
711    @Override
712    public void onPackagesAvailable(String[] packageNames, UserHandle user,
713            boolean replacing) {
714        enqueueModelUpdateTask(
715                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
716    }
717
718    @Override
719    public void onPackagesUnavailable(String[] packageNames, UserHandle user,
720            boolean replacing) {
721        if (!replacing) {
722            enqueueModelUpdateTask(new PackageUpdatedTask(
723                    PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
724        }
725    }
726
727    @Override
728    public void onPackagesSuspended(String[] packageNames, UserHandle user) {
729        enqueueModelUpdateTask(new PackageUpdatedTask(
730                PackageUpdatedTask.OP_SUSPEND, user, packageNames));
731    }
732
733    @Override
734    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
735        enqueueModelUpdateTask(new PackageUpdatedTask(
736                PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
737    }
738
739    @Override
740    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
741            UserHandle user) {
742        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
743    }
744
745    public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
746            UserHandle user) {
747        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
748    }
749
750    /**
751     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
752     * ACTION_PACKAGE_CHANGED.
753     */
754    @Override
755    public void onReceive(Context context, Intent intent) {
756        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
757
758        final String action = intent.getAction();
759        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
760            // If we have changed locale we need to clear out the labels in all apps/workspace.
761            forceReload();
762        } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
763                || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
764            UserManagerCompat.getInstance(context).enableAndResetCache();
765            forceReload();
766        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
767                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
768                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
769            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
770            if (user != null) {
771                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
772                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
773                    enqueueModelUpdateTask(new PackageUpdatedTask(
774                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
775                }
776
777                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
778                // we need to run the state change task again.
779                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
780                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
781                    enqueueModelUpdateTask(new UserLockStateChangedTask(user));
782                }
783            }
784        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
785            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
786        }
787    }
788
789    void forceReload() {
790        resetLoadedState(true, true);
791
792        // Do this here because if the launcher activity is running it will be restarted.
793        // If it's not running startLoaderFromBackground will merely tell it that it needs
794        // to reload.
795        startLoaderFromBackground();
796    }
797
798    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
799        synchronized (mLock) {
800            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
801            // mWorkspaceLoaded to true later
802            stopLoaderLocked();
803            if (resetAllAppsLoaded) mAllAppsLoaded = false;
804            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
805            // Always reset deep shortcuts loaded.
806            // TODO: why?
807            mDeepShortcutsLoaded = false;
808        }
809    }
810
811    /**
812     * When the launcher is in the background, it's possible for it to miss paired
813     * configuration changes.  So whenever we trigger the loader from the background
814     * tell the launcher that it needs to re-run the loader when it comes back instead
815     * of doing it now.
816     */
817    public void startLoaderFromBackground() {
818        Callbacks callbacks = getCallback();
819        if (callbacks != null) {
820            // Only actually run the loader if they're not paused.
821            if (!callbacks.setLoadOnResume()) {
822                startLoader(callbacks.getCurrentWorkspaceScreen());
823            }
824        }
825    }
826
827    /**
828     * If there is already a loader task running, tell it to stop.
829     */
830    private void stopLoaderLocked() {
831        LoaderTask oldTask = mLoaderTask;
832        if (oldTask != null) {
833            oldTask.stopLocked();
834        }
835    }
836
837    public boolean isCurrentCallbacks(Callbacks callbacks) {
838        return (mCallbacks != null && mCallbacks.get() == callbacks);
839    }
840
841    /**
842     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
843     * @return true if the page could be bound synchronously.
844     */
845    public boolean startLoader(int synchronousBindPage) {
846        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
847        InstallShortcutReceiver.enableInstallQueue();
848        synchronized (mLock) {
849            // Don't bother to start the thread if we know it's not going to do anything
850            if (mCallbacks != null && mCallbacks.get() != null) {
851                final Callbacks oldCallbacks = mCallbacks.get();
852                // Clear any pending bind-runnables from the synchronized load process.
853                runOnMainThread(new Runnable() {
854                    public void run() {
855                        oldCallbacks.clearPendingBinds();
856                    }
857                });
858
859                // If there is already one running, tell it to stop.
860                stopLoaderLocked();
861                mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
862                // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
863                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
864                        && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
865                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
866                    return true;
867                } else {
868                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
869                    sWorker.post(mLoaderTask);
870                }
871            }
872        }
873        return false;
874    }
875
876    public void stopLoader() {
877        synchronized (mLock) {
878            if (mLoaderTask != null) {
879                mLoaderTask.stopLocked();
880            }
881        }
882    }
883
884    /**
885     * Loads the workspace screen ids in an ordered list.
886     */
887    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
888        final ContentResolver contentResolver = context.getContentResolver();
889        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
890
891        // Get screens ordered by rank.
892        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
893                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
894    }
895
896    /**
897     * Runnable for the thread that loads the contents of the launcher:
898     *   - workspace icons
899     *   - widgets
900     *   - all apps icons
901     *   - deep shortcuts within apps
902     */
903    private class LoaderTask implements Runnable {
904        private Context mContext;
905        private int mPageToBindFirst;
906
907        @Thunk boolean mIsLoadingAndBindingWorkspace;
908        private boolean mStopped;
909        @Thunk boolean mLoadAndBindStepFinished;
910
911        LoaderTask(Context context, int pageToBindFirst) {
912            mContext = context;
913            mPageToBindFirst = pageToBindFirst;
914        }
915
916        private void loadAndBindWorkspace() {
917            mIsLoadingAndBindingWorkspace = true;
918
919            // Load the workspace
920            if (DEBUG_LOADERS) {
921                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
922            }
923
924            if (!mWorkspaceLoaded) {
925                loadWorkspace();
926                synchronized (LoaderTask.this) {
927                    if (mStopped) {
928                        return;
929                    }
930                    mWorkspaceLoaded = true;
931                }
932            }
933
934            // Bind the workspace
935            bindWorkspace(mPageToBindFirst);
936        }
937
938        private void waitForIdle() {
939            // Wait until the either we're stopped or the other threads are done.
940            // This way we don't start loading all apps until the workspace has settled
941            // down.
942            synchronized (LoaderTask.this) {
943                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
944
945                mHandler.postIdle(new Runnable() {
946                        public void run() {
947                            synchronized (LoaderTask.this) {
948                                mLoadAndBindStepFinished = true;
949                                if (DEBUG_LOADERS) {
950                                    Log.d(TAG, "done with previous binding step");
951                                }
952                                LoaderTask.this.notify();
953                            }
954                        }
955                    });
956
957                while (!mStopped && !mLoadAndBindStepFinished) {
958                    try {
959                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
960                        // wait no longer than 1sec at a time
961                        this.wait(1000);
962                    } catch (InterruptedException ex) {
963                        // Ignore
964                    }
965                }
966                if (DEBUG_LOADERS) {
967                    Log.d(TAG, "waited "
968                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
969                            + "ms for previous step to finish binding");
970                }
971            }
972        }
973
974        void runBindSynchronousPage(int synchronousBindPage) {
975            if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
976                // Ensure that we have a valid page index to load synchronously
977                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
978                        "valid page index");
979            }
980            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
981                // Ensure that we don't try and bind a specified page when the pages have not been
982                // loaded already (we should load everything asynchronously in that case)
983                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
984            }
985            synchronized (mLock) {
986                if (mIsLoaderTaskRunning) {
987                    // Ensure that we are never running the background loading at this point since
988                    // we also touch the background collections
989                    throw new RuntimeException("Error! Background loading is already running");
990                }
991            }
992
993            // XXX: Throw an exception if we are already loading (since we touch the worker thread
994            //      data structures, we can't allow any other thread to touch that data, but because
995            //      this call is synchronous, we can get away with not locking).
996
997            // The LauncherModel is static in the LauncherAppState and mHandler may have queued
998            // operations from the previous activity.  We need to ensure that all queued operations
999            // are executed before any synchronous binding work is done.
1000            mHandler.flush();
1001
1002            // Divide the set of loaded items into those that we are binding synchronously, and
1003            // everything else that is to be bound normally (asynchronously).
1004            bindWorkspace(synchronousBindPage);
1005            // XXX: For now, continue posting the binding of AllApps as there are other issues that
1006            //      arise from that.
1007            onlyBindAllApps();
1008
1009            bindDeepShortcuts();
1010        }
1011
1012        public void run() {
1013            synchronized (mLock) {
1014                if (mStopped) {
1015                    return;
1016                }
1017                mIsLoaderTaskRunning = true;
1018            }
1019            // Optimize for end-user experience: if the Launcher is up and // running with the
1020            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1021            // workspace first (default).
1022            keep_running: {
1023                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1024                loadAndBindWorkspace();
1025
1026                if (mStopped) {
1027                    break keep_running;
1028                }
1029
1030                waitForIdle();
1031
1032                // second step
1033                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1034                loadAndBindAllApps();
1035
1036                waitForIdle();
1037
1038                // third step
1039                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
1040                loadAndBindDeepShortcuts();
1041            }
1042
1043            // Clear out this reference, otherwise we end up holding it until all of the
1044            // callback runnables are done.
1045            mContext = null;
1046
1047            synchronized (mLock) {
1048                // If we are still the last one to be scheduled, remove ourselves.
1049                if (mLoaderTask == this) {
1050                    mLoaderTask = null;
1051                }
1052                mIsLoaderTaskRunning = false;
1053                mHasLoaderCompletedOnce = true;
1054            }
1055        }
1056
1057        public void stopLocked() {
1058            synchronized (LoaderTask.this) {
1059                mStopped = true;
1060                this.notify();
1061            }
1062        }
1063
1064        /**
1065         * Gets the callbacks object.  If we've been stopped, or if the launcher object
1066         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
1067         * object that was around when the deferred message was scheduled, and if there's
1068         * a new Callbacks object around then also return null.  This will save us from
1069         * calling onto it with data that will be ignored.
1070         */
1071        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1072            synchronized (mLock) {
1073                if (mStopped) {
1074                    return null;
1075                }
1076
1077                if (mCallbacks == null) {
1078                    return null;
1079                }
1080
1081                final Callbacks callbacks = mCallbacks.get();
1082                if (callbacks != oldCallbacks) {
1083                    return null;
1084                }
1085                if (callbacks == null) {
1086                    Log.w(TAG, "no mCallbacks");
1087                    return null;
1088                }
1089
1090                return callbacks;
1091            }
1092        }
1093
1094        private void loadWorkspace() {
1095            if (LauncherAppState.PROFILE_STARTUP) {
1096                Trace.beginSection("Loading Workspace");
1097            }
1098            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1099
1100            final Context context = mContext;
1101            final ContentResolver contentResolver = context.getContentResolver();
1102            final PackageManager manager = context.getPackageManager();
1103            final boolean isSafeMode = manager.isSafeMode();
1104            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1105            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
1106            final boolean isSdCardReady = Utilities.isBootCompleted();
1107            final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
1108
1109            boolean clearDb = false;
1110            try {
1111                ImportDataTask.performImportIfPossible(context);
1112            } catch (Exception e) {
1113                // Migration failed. Clear workspace.
1114                clearDb = true;
1115            }
1116
1117            if (!clearDb && GridSizeMigrationTask.ENABLED &&
1118                    !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
1119                // Migration failed. Clear workspace.
1120                clearDb = true;
1121            }
1122
1123            if (clearDb) {
1124                Log.d(TAG, "loadWorkspace: resetting launcher database");
1125                LauncherSettings.Settings.call(contentResolver,
1126                        LauncherSettings.Settings.METHOD_DELETE_DB);
1127            }
1128
1129            Log.d(TAG, "loadWorkspace: loading default favorites");
1130            LauncherSettings.Settings.call(contentResolver,
1131                    LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
1132
1133            synchronized (sBgDataModel) {
1134                sBgDataModel.clear();
1135
1136                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1137                        .getInstance(mContext).updateAndGetActiveSessionCache();
1138                sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
1139
1140                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
1141                final LoaderCursor c = new LoaderCursor(contentResolver.query(
1142                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
1143
1144                HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
1145
1146                try {
1147                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1148                            LauncherSettings.Favorites.APPWIDGET_ID);
1149                    final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1150                            LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1151                    final int spanXIndex = c.getColumnIndexOrThrow
1152                            (LauncherSettings.Favorites.SPANX);
1153                    final int spanYIndex = c.getColumnIndexOrThrow(
1154                            LauncherSettings.Favorites.SPANY);
1155                    final int rankIndex = c.getColumnIndexOrThrow(
1156                            LauncherSettings.Favorites.RANK);
1157                    final int optionsIndex = c.getColumnIndexOrThrow(
1158                            LauncherSettings.Favorites.OPTIONS);
1159
1160                    final LongSparseArray<UserHandle> allUsers = c.allUsers;
1161                    final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
1162                    final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
1163                    for (UserHandle user : mUserManager.getUserProfiles()) {
1164                        long serialNo = mUserManager.getSerialNumberForUser(user);
1165                        allUsers.put(serialNo, user);
1166                        quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
1167
1168                        boolean userUnlocked = mUserManager.isUserUnlocked(user);
1169
1170                        // We can only query for shortcuts when the user is unlocked.
1171                        if (userUnlocked) {
1172                            List<ShortcutInfoCompat> pinnedShortcuts =
1173                                    shortcutManager.queryForPinnedShortcuts(null, user);
1174                            if (shortcutManager.wasLastCallSuccess()) {
1175                                for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
1176                                    shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
1177                                            shortcut);
1178                                }
1179                            } else {
1180                                // Shortcut manager can fail due to some race condition when the
1181                                // lock state changes too frequently. For the purpose of the loading
1182                                // shortcuts, consider the user is still locked.
1183                                userUnlocked = false;
1184                            }
1185                        }
1186                        unlockedUsers.put(serialNo, userUnlocked);
1187                    }
1188
1189                    ShortcutInfo info;
1190                    String intentDescription;
1191                    LauncherAppWidgetInfo appWidgetInfo;
1192                    Intent intent;
1193                    String targetPkg;
1194
1195                    while (!mStopped && c.moveToNext()) {
1196                        try {
1197                            if (c.user == null) {
1198                                // User has been deleted, remove the item.
1199                                c.markDeleted("User has been deleted");
1200                                continue;
1201                            }
1202
1203                            boolean allowMissingTarget = false;
1204                            switch (c.itemType) {
1205                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1206                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1207                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1208                                intent = c.parseIntent();
1209                                if (intent == null) {
1210                                    c.markDeleted("Invalid or null intent");
1211                                    continue;
1212                                }
1213
1214                                int disabledState = quietMode.get(c.serialNumber) ?
1215                                        ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0;
1216                                ComponentName cn = intent.getComponent();
1217                                targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
1218
1219                                if (!Process.myUserHandle().equals(c.user)) {
1220                                    if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
1221                                        c.markDeleted("Legacy shortcuts are only allowed for default user");
1222                                        continue;
1223                                    } else if (c.restoreFlag != 0) {
1224                                        // Don't restore items for other profiles.
1225                                        c.markDeleted("Restore from managed profile not supported");
1226                                        continue;
1227                                    }
1228                                }
1229                                if (TextUtils.isEmpty(targetPkg) &&
1230                                        c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
1231                                    c.markDeleted("Only legacy shortcuts can have null package");
1232                                    continue;
1233                                }
1234
1235                                // If there is no target package, its an implicit intent
1236                                // (legacy shortcut) which is always valid
1237                                boolean validTarget = TextUtils.isEmpty(targetPkg) ||
1238                                        launcherApps.isPackageEnabledForProfile(targetPkg, c.user);
1239
1240                                if (cn != null && validTarget) {
1241                                    // If the apk is present and the shortcut points to a specific
1242                                    // component.
1243
1244                                    // If the component is already present
1245                                    if (launcherApps.isActivityEnabledForProfile(cn, c.user)) {
1246                                        // no special handling necessary for this item
1247                                        c.markRestored();
1248                                    } else {
1249                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
1250                                            // We allow auto install apps to have their intent
1251                                            // updated after an install.
1252                                            intent = manager.getLaunchIntentForPackage(targetPkg);
1253                                            if (intent != null) {
1254                                                c.restoreFlag = 0;
1255                                                c.updater().put(
1256                                                        LauncherSettings.Favorites.INTENT,
1257                                                        intent.toUri(0)).commit();
1258                                                cn = intent.getComponent();
1259                                            } else {
1260                                                c.markDeleted("Unable to find a launch target");
1261                                                continue;
1262                                            }
1263                                        } else {
1264                                            // The app is installed but the component is no
1265                                            // longer available.
1266                                            c.markDeleted("Invalid component removed: " + cn);
1267                                            continue;
1268                                        }
1269                                    }
1270                                }
1271                                // else if cn == null => can't infer much, leave it
1272                                // else if !validPkg => could be restored icon or missing sd-card
1273
1274                                if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
1275                                    // Points to a valid app (superset of cn != null) but the apk
1276                                    // is not available.
1277
1278                                    if (c.restoreFlag != 0) {
1279                                        // Package is not yet available but might be
1280                                        // installed later.
1281                                        FileLog.d(TAG, "package not yet restored: " + targetPkg);
1282
1283                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) {
1284                                            // Restore has started once.
1285                                        } else if (installingPkgs.containsKey(targetPkg)) {
1286                                            // App restore has started. Update the flag
1287                                            c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED;
1288                                            c.updater().commit();
1289                                        } else {
1290                                            c.markDeleted("Unrestored app removed: " + targetPkg);
1291                                            continue;
1292                                        }
1293                                    } else if (PackageManagerHelper.isAppOnSdcard(
1294                                            manager, targetPkg)) {
1295                                        // Package is present but not available.
1296                                        disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1297                                        // Add the icon on the workspace anyway.
1298                                        allowMissingTarget = true;
1299                                    } else if (!isSdCardReady) {
1300                                        // SdCard is not ready yet. Package might get available,
1301                                        // once it is ready.
1302                                        Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
1303                                        pendingPackages.addToList(c.user, targetPkg);
1304                                        // Add the icon on the workspace anyway.
1305                                        allowMissingTarget = true;
1306                                    } else {
1307                                        // Do not wait for external media load anymore.
1308                                        c.markDeleted("Invalid package removed: " + targetPkg);
1309                                        continue;
1310                                    }
1311                                }
1312
1313                                if (validTarget) {
1314                                    // The shortcut points to a valid target (either no target
1315                                    // or something which is ready to be used)
1316                                    c.markRestored();
1317                                }
1318
1319                                boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
1320                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1321
1322                                if (c.restoreFlag != 0) {
1323                                    // Already verified above that user is same as default user
1324                                    info = c.getRestoredItemInfo(intent);
1325                                } else if (c.itemType ==
1326                                        LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1327                                    info = c.getAppShortcutInfo(
1328                                            intent, allowMissingTarget, useLowResIcon);
1329                                } else if (c.itemType ==
1330                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1331
1332                                    ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
1333                                    if (unlockedUsers.get(c.serialNumber)) {
1334                                        ShortcutInfoCompat pinnedShortcut =
1335                                                shortcutKeyToPinnedShortcuts.get(key);
1336                                        if (pinnedShortcut == null) {
1337                                            // The shortcut is no longer valid.
1338                                            c.markDeleted("Pinned shortcut not found");
1339                                            continue;
1340                                        }
1341                                        info = new ShortcutInfo(pinnedShortcut, context);
1342                                        info.iconBitmap = LauncherIcons
1343                                                .createShortcutIcon(pinnedShortcut, context);
1344                                        intent = info.intent;
1345                                    } else {
1346                                        // Create a shortcut info in disabled mode for now.
1347                                        info = c.loadSimpleShortcut();
1348                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1349                                    }
1350                                } else { // item type == ITEM_TYPE_SHORTCUT
1351                                    info = c.loadSimpleShortcut();
1352
1353                                    // Shortcuts are only available on the primary profile
1354                                    if (PackageManagerHelper.isAppSuspended(manager, targetPkg)) {
1355                                        disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1356                                    }
1357
1358                                    // App shortcuts that used to be automatically added to Launcher
1359                                    // didn't always have the correct intent flags set, so do that
1360                                    // here
1361                                    if (intent.getAction() != null &&
1362                                        intent.getCategories() != null &&
1363                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
1364                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1365                                        intent.addFlags(
1366                                            Intent.FLAG_ACTIVITY_NEW_TASK |
1367                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1368                                    }
1369                                }
1370
1371                                if (info != null) {
1372                                    c.applyCommonProperties(info);
1373
1374                                    info.intent = intent;
1375                                    info.rank = c.getInt(rankIndex);
1376                                    info.spanX = 1;
1377                                    info.spanY = 1;
1378                                    // TODO: Remove this extra. Instead we should be using
1379                                    // itemInfo#user.
1380                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, c.serialNumber);
1381                                    info.isDisabled |= disabledState;
1382                                    if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
1383                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1384                                    }
1385
1386                                    if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
1387                                        Integer progress = installingPkgs.get(targetPkg);
1388                                        if (progress != null) {
1389                                            info.setInstallProgress(progress);
1390                                        } else {
1391                                            info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
1392                                        }
1393                                    }
1394
1395                                    c.checkAndAddItem(info, sBgDataModel);
1396                                } else {
1397                                    throw new RuntimeException("Unexpected null ShortcutInfo");
1398                                }
1399                                break;
1400
1401                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1402                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id);
1403                                c.applyCommonProperties(folderInfo);
1404
1405                                // Do not trim the folder label, as is was set by the user.
1406                                folderInfo.title = c.getString(c.titleIndex);
1407                                folderInfo.spanX = 1;
1408                                folderInfo.spanY = 1;
1409                                folderInfo.options = c.getInt(optionsIndex);
1410
1411                                // no special handling required for restored folders
1412                                c.markRestored();
1413
1414                                c.checkAndAddItem(folderInfo, sBgDataModel);
1415                                break;
1416
1417                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1418                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1419                                // Read all Launcher-specific widget details
1420                                boolean customWidget = c.itemType ==
1421                                    LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1422
1423                                int appWidgetId = c.getInt(appWidgetIdIndex);
1424                                String savedProvider = c.getString(appWidgetProviderIndex);
1425
1426                                final ComponentName component =
1427                                        ComponentName.unflattenFromString(savedProvider);
1428
1429                                final boolean isIdValid = !c.hasRestoreFlag(
1430                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
1431                                final boolean wasProviderReady = !c.hasRestoreFlag(
1432                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
1433
1434                                if (widgetProvidersMap == null) {
1435                                    widgetProvidersMap = AppWidgetManagerCompat
1436                                            .getInstance(mContext).getAllProvidersMap();
1437                                }
1438                                final AppWidgetProviderInfo provider = widgetProvidersMap.get(
1439                                        new ComponentKey(
1440                                                ComponentName.unflattenFromString(savedProvider),
1441                                                c.user));
1442
1443                                final boolean isProviderReady = isValidProvider(provider);
1444                                if (!isSafeMode && !customWidget &&
1445                                        wasProviderReady && !isProviderReady) {
1446                                    c.markDeleted(
1447                                            "Deleting widget that isn't installed anymore: "
1448                                            + provider);
1449                                } else {
1450                                    if (isProviderReady) {
1451                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1452                                                provider.provider);
1453
1454                                        // The provider is available. So the widget is either
1455                                        // available or not available. We do not need to track
1456                                        // any future restore updates.
1457                                        int status = c.restoreFlag &
1458                                                ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1459                                        if (!wasProviderReady) {
1460                                            // If provider was not previously ready, update the
1461                                            // status and UI flag.
1462
1463                                            // Id would be valid only if the widget restore broadcast was received.
1464                                            if (isIdValid) {
1465                                                status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
1466                                            } else {
1467                                                status &= ~LauncherAppWidgetInfo
1468                                                        .FLAG_PROVIDER_NOT_READY;
1469                                            }
1470                                        }
1471                                        appWidgetInfo.restoreStatus = status;
1472                                    } else {
1473                                        Log.v(TAG, "Widget restore pending id=" + c.id
1474                                                + " appWidgetId=" + appWidgetId
1475                                                + " status =" + c.restoreFlag);
1476                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1477                                                component);
1478                                        appWidgetInfo.restoreStatus = c.restoreFlag;
1479                                        Integer installProgress = installingPkgs.get(component.getPackageName());
1480
1481                                        if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
1482                                            // Restore has started once.
1483                                        } else if (installProgress != null) {
1484                                            // App restore has started. Update the flag
1485                                            appWidgetInfo.restoreStatus |=
1486                                                    LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1487                                        } else if (!isSafeMode) {
1488                                            c.markDeleted("Unrestored widget removed: " + component);
1489                                            continue;
1490                                        }
1491
1492                                        appWidgetInfo.installProgress =
1493                                                installProgress == null ? 0 : installProgress;
1494                                    }
1495                                    if (appWidgetInfo.hasRestoreFlag(
1496                                            LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
1497                                        appWidgetInfo.bindOptions = c.parseIntent();
1498                                    }
1499
1500                                    c.applyCommonProperties(appWidgetInfo);
1501                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
1502                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
1503                                    appWidgetInfo.user = c.user;
1504
1505                                    if (!c.isOnWorkspaceOrHotseat()) {
1506                                        c.markDeleted("Widget found where container != " +
1507                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1508                                        continue;
1509                                    }
1510
1511                                    if (!customWidget) {
1512                                        String providerName =
1513                                                appWidgetInfo.providerName.flattenToString();
1514                                        if (!providerName.equals(savedProvider) ||
1515                                                (appWidgetInfo.restoreStatus != c.restoreFlag)) {
1516                                            c.updater()
1517                                                    .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
1518                                                            providerName)
1519                                                    .put(LauncherSettings.Favorites.RESTORED,
1520                                                            appWidgetInfo.restoreStatus)
1521                                                    .commit();
1522                                        }
1523                                    }
1524                                    c.checkAndAddItem(appWidgetInfo, sBgDataModel);
1525                                }
1526                                break;
1527                            }
1528                        } catch (Exception e) {
1529                            Log.e(TAG, "Desktop items loading interrupted", e);
1530                        }
1531                    }
1532                } finally {
1533                    Utilities.closeSilently(c);
1534                }
1535
1536                // Break early if we've stopped loading
1537                if (mStopped) {
1538                    sBgDataModel.clear();
1539                    return;
1540                }
1541
1542                // Remove dead items
1543                if (c.commitDeleted()) {
1544                    // Remove any empty folder
1545                    ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
1546                            .call(contentResolver,
1547                                    LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
1548                            .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
1549                    for (long folderId : deletedFolderIds) {
1550                        sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
1551                        sBgDataModel.folders.remove(folderId);
1552                        sBgDataModel.itemsIdMap.remove(folderId);
1553                    }
1554                }
1555
1556                // Unpin shortcuts that don't exist on the workspace.
1557                HashSet<ShortcutKey> pendingShortcuts =
1558                        InstallShortcutReceiver.getPendingShortcuts(context);
1559                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
1560                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
1561                    if ((numTimesPinned == null || numTimesPinned.value == 0)
1562                            && !pendingShortcuts.contains(key)) {
1563                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
1564                        shortcutManager.unpinShortcut(key);
1565                    }
1566                }
1567
1568                // Sort all the folder items and make sure the first 3 items are high resolution.
1569                for (FolderInfo folder : sBgDataModel.folders) {
1570                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
1571                    int pos = 0;
1572                    for (ShortcutInfo info : folder.contents) {
1573                        if (info.usingLowResIcon &&
1574                                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1575                            mIconCache.getTitleAndIcon(info, false);
1576                        }
1577                        pos ++;
1578                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
1579                            break;
1580                        }
1581                    }
1582                }
1583
1584                c.commitRestoredItems();
1585                if (!isSdCardReady && !pendingPackages.isEmpty()) {
1586                    context.registerReceiver(
1587                            new SdCardAvailableReceiver(
1588                                    LauncherModel.this, mContext, pendingPackages),
1589                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
1590                            null,
1591                            sWorker);
1592                }
1593
1594                // Remove any empty screens
1595                ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens);
1596                for (ItemInfo item: sBgDataModel.itemsIdMap) {
1597                    long screenId = item.screenId;
1598                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1599                            unusedScreens.contains(screenId)) {
1600                        unusedScreens.remove(screenId);
1601                    }
1602                }
1603
1604                // If there are any empty screens remove them, and update.
1605                if (unusedScreens.size() != 0) {
1606                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
1607                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
1608                }
1609            }
1610            if (LauncherAppState.PROFILE_STARTUP) {
1611                Trace.endSection();
1612            }
1613        }
1614
1615        /** Filters the set of items who are directly or indirectly (via another container) on the
1616         * specified screen. */
1617        private void filterCurrentWorkspaceItems(long currentScreenId,
1618                ArrayList<ItemInfo> allWorkspaceItems,
1619                ArrayList<ItemInfo> currentScreenItems,
1620                ArrayList<ItemInfo> otherScreenItems) {
1621            // Purge any null ItemInfos
1622            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
1623            while (iter.hasNext()) {
1624                ItemInfo i = iter.next();
1625                if (i == null) {
1626                    iter.remove();
1627                }
1628            }
1629
1630            // Order the set of items by their containers first, this allows use to walk through the
1631            // list sequentially, build up a list of containers that are in the specified screen,
1632            // as well as all items in those containers.
1633            Set<Long> itemsOnScreen = new HashSet<Long>();
1634            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
1635                @Override
1636                public int compare(ItemInfo lhs, ItemInfo rhs) {
1637                    return Utilities.longCompare(lhs.container, rhs.container);
1638                }
1639            });
1640            for (ItemInfo info : allWorkspaceItems) {
1641                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1642                    if (info.screenId == currentScreenId) {
1643                        currentScreenItems.add(info);
1644                        itemsOnScreen.add(info.id);
1645                    } else {
1646                        otherScreenItems.add(info);
1647                    }
1648                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1649                    currentScreenItems.add(info);
1650                    itemsOnScreen.add(info.id);
1651                } else {
1652                    if (itemsOnScreen.contains(info.container)) {
1653                        currentScreenItems.add(info);
1654                        itemsOnScreen.add(info.id);
1655                    } else {
1656                        otherScreenItems.add(info);
1657                    }
1658                }
1659            }
1660        }
1661
1662        /** Filters the set of widgets which are on the specified screen. */
1663        private void filterCurrentAppWidgets(long currentScreenId,
1664                ArrayList<LauncherAppWidgetInfo> appWidgets,
1665                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
1666                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
1667
1668            for (LauncherAppWidgetInfo widget : appWidgets) {
1669                if (widget == null) continue;
1670                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1671                        widget.screenId == currentScreenId) {
1672                    currentScreenWidgets.add(widget);
1673                } else {
1674                    otherScreenWidgets.add(widget);
1675                }
1676            }
1677        }
1678
1679        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
1680         * right) */
1681        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
1682            final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
1683            final int screenCols = profile.numColumns;
1684            final int screenCellCount = profile.numColumns * profile.numRows;
1685            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
1686                @Override
1687                public int compare(ItemInfo lhs, ItemInfo rhs) {
1688                    if (lhs.container == rhs.container) {
1689                        // Within containers, order by their spatial position in that container
1690                        switch ((int) lhs.container) {
1691                            case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
1692                                long lr = (lhs.screenId * screenCellCount +
1693                                        lhs.cellY * screenCols + lhs.cellX);
1694                                long rr = (rhs.screenId * screenCellCount +
1695                                        rhs.cellY * screenCols + rhs.cellX);
1696                                return Utilities.longCompare(lr, rr);
1697                            }
1698                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
1699                                // We currently use the screen id as the rank
1700                                return Utilities.longCompare(lhs.screenId, rhs.screenId);
1701                            }
1702                            default:
1703                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
1704                                    throw new RuntimeException("Unexpected container type when " +
1705                                            "sorting workspace items.");
1706                                }
1707                                return 0;
1708                        }
1709                    } else {
1710                        // Between containers, order by hotseat, desktop
1711                        return Utilities.longCompare(lhs.container, rhs.container);
1712                    }
1713                }
1714            });
1715        }
1716
1717        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
1718                final ArrayList<Long> orderedScreens) {
1719            final Runnable r = new Runnable() {
1720                @Override
1721                public void run() {
1722                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1723                    if (callbacks != null) {
1724                        callbacks.bindScreens(orderedScreens);
1725                    }
1726                }
1727            };
1728            runOnMainThread(r);
1729        }
1730
1731        private void bindWorkspaceItems(final Callbacks oldCallbacks,
1732                final ArrayList<ItemInfo> workspaceItems,
1733                final ArrayList<LauncherAppWidgetInfo> appWidgets,
1734                final Executor executor) {
1735
1736            // Bind the workspace items
1737            int N = workspaceItems.size();
1738            for (int i = 0; i < N; i += ITEMS_CHUNK) {
1739                final int start = i;
1740                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1741                final Runnable r = new Runnable() {
1742                    @Override
1743                    public void run() {
1744                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1745                        if (callbacks != null) {
1746                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
1747                                    false);
1748                        }
1749                    }
1750                };
1751                executor.execute(r);
1752            }
1753
1754            // Bind the widgets, one at a time
1755            N = appWidgets.size();
1756            for (int i = 0; i < N; i++) {
1757                final LauncherAppWidgetInfo widget = appWidgets.get(i);
1758                final Runnable r = new Runnable() {
1759                    public void run() {
1760                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1761                        if (callbacks != null) {
1762                            callbacks.bindAppWidget(widget);
1763                        }
1764                    }
1765                };
1766                executor.execute(r);
1767            }
1768        }
1769
1770        /**
1771         * Binds all loaded data to actual views on the main thread.
1772         */
1773        private void bindWorkspace(int synchronizeBindPage) {
1774            final long t = SystemClock.uptimeMillis();
1775            Runnable r;
1776
1777            // Don't use these two variables in any of the callback runnables.
1778            // Otherwise we hold a reference to them.
1779            final Callbacks oldCallbacks = mCallbacks.get();
1780            if (oldCallbacks == null) {
1781                // This launcher has exited and nobody bothered to tell us.  Just bail.
1782                Log.w(TAG, "LoaderTask running with no launcher");
1783                return;
1784            }
1785
1786            // Save a copy of all the bg-thread collections
1787            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
1788            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
1789            ArrayList<Long> orderedScreenIds = new ArrayList<>();
1790
1791            synchronized (sBgDataModel) {
1792                workspaceItems.addAll(sBgDataModel.workspaceItems);
1793                appWidgets.addAll(sBgDataModel.appWidgets);
1794                orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
1795            }
1796
1797            final int currentScreen;
1798            {
1799                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
1800                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
1801                if (currScreen >= orderedScreenIds.size()) {
1802                    // There may be no workspace screens (just hotseat items and an empty page).
1803                    currScreen = PagedView.INVALID_RESTORE_PAGE;
1804                }
1805                currentScreen = currScreen;
1806            }
1807            final boolean validFirstPage = currentScreen >= 0;
1808            final long currentScreenId =
1809                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
1810
1811            // Separate the items that are on the current screen, and all the other remaining items
1812            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
1813            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
1814            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
1815            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
1816
1817            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
1818                    otherWorkspaceItems);
1819            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
1820                    otherAppWidgets);
1821            sortWorkspaceItemsSpatially(currentWorkspaceItems);
1822            sortWorkspaceItemsSpatially(otherWorkspaceItems);
1823
1824            // Tell the workspace that we're about to start binding items
1825            r = new Runnable() {
1826                public void run() {
1827                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1828                    if (callbacks != null) {
1829                        callbacks.clearPendingBinds();
1830                        callbacks.startBinding();
1831                    }
1832                }
1833            };
1834            runOnMainThread(r);
1835
1836            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
1837
1838            Executor mainExecutor = new DeferredMainThreadExecutor();
1839            // Load items on the current page.
1840            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
1841
1842            // In case of validFirstPage, only bind the first screen, and defer binding the
1843            // remaining screens after first onDraw (and an optional the fade animation whichever
1844            // happens later).
1845            // This ensures that the first screen is immediately visible (eg. during rotation)
1846            // In case of !validFirstPage, bind all pages one after other.
1847            final Executor deferredExecutor =
1848                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
1849
1850            mainExecutor.execute(new Runnable() {
1851                @Override
1852                public void run() {
1853                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1854                    if (callbacks != null) {
1855                        callbacks.finishFirstPageBind(
1856                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
1857                    }
1858                }
1859            });
1860
1861            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
1862
1863            // Tell the workspace that we're done binding items
1864            r = new Runnable() {
1865                public void run() {
1866                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1867                    if (callbacks != null) {
1868                        callbacks.finishBindingItems();
1869                    }
1870
1871                    mIsLoadingAndBindingWorkspace = false;
1872
1873                    // Run all the bind complete runnables after workspace is bound.
1874                    if (!mBindCompleteRunnables.isEmpty()) {
1875                        synchronized (mBindCompleteRunnables) {
1876                            for (final Runnable r : mBindCompleteRunnables) {
1877                                runOnWorkerThread(r);
1878                            }
1879                            mBindCompleteRunnables.clear();
1880                        }
1881                    }
1882
1883                    // If we're profiling, ensure this is the last thing in the queue.
1884                    if (DEBUG_LOADERS) {
1885                        Log.d(TAG, "bound workspace in "
1886                            + (SystemClock.uptimeMillis()-t) + "ms");
1887                    }
1888
1889                }
1890            };
1891            deferredExecutor.execute(r);
1892
1893            if (validFirstPage) {
1894                r = new Runnable() {
1895                    public void run() {
1896                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1897                        if (callbacks != null) {
1898                            // We are loading synchronously, which means, some of the pages will be
1899                            // bound after first draw. Inform the callbacks that page binding is
1900                            // not complete, and schedule the remaining pages.
1901                            if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1902                                callbacks.onPageBoundSynchronously(currentScreen);
1903                            }
1904                            callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
1905                        }
1906                    }
1907                };
1908                runOnMainThread(r);
1909            }
1910        }
1911
1912        private void loadAndBindAllApps() {
1913            if (DEBUG_LOADERS) {
1914                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
1915            }
1916            if (!mAllAppsLoaded) {
1917                loadAllApps();
1918                synchronized (LoaderTask.this) {
1919                    if (mStopped) {
1920                        return;
1921                    }
1922                }
1923                updateIconCache();
1924                synchronized (LoaderTask.this) {
1925                    if (mStopped) {
1926                        return;
1927                    }
1928                    mAllAppsLoaded = true;
1929                }
1930            } else {
1931                onlyBindAllApps();
1932            }
1933        }
1934
1935        private void updateIconCache() {
1936            // Ignore packages which have a promise icon.
1937            HashSet<String> packagesToIgnore = new HashSet<>();
1938            synchronized (sBgDataModel) {
1939                for (ItemInfo info : sBgDataModel.itemsIdMap) {
1940                    if (info instanceof ShortcutInfo) {
1941                        ShortcutInfo si = (ShortcutInfo) info;
1942                        if (si.isPromise() && si.getTargetComponent() != null) {
1943                            packagesToIgnore.add(si.getTargetComponent().getPackageName());
1944                        }
1945                    } else if (info instanceof LauncherAppWidgetInfo) {
1946                        LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
1947                        if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
1948                            packagesToIgnore.add(lawi.providerName.getPackageName());
1949                        }
1950                    }
1951                }
1952            }
1953            mIconCache.updateDbIcons(packagesToIgnore);
1954        }
1955
1956        private void onlyBindAllApps() {
1957            final Callbacks oldCallbacks = mCallbacks.get();
1958            if (oldCallbacks == null) {
1959                // This launcher has exited and nobody bothered to tell us.  Just bail.
1960                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
1961                return;
1962            }
1963
1964            // shallow copy
1965            @SuppressWarnings("unchecked")
1966            final ArrayList<AppInfo> list
1967                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
1968            Runnable r = new Runnable() {
1969                public void run() {
1970                    final long t = SystemClock.uptimeMillis();
1971                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1972                    if (callbacks != null) {
1973                        callbacks.bindAllApplications(list);
1974                    }
1975                    if (DEBUG_LOADERS) {
1976                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1977                                + (SystemClock.uptimeMillis() - t) + "ms");
1978                    }
1979                }
1980            };
1981            runOnMainThread(r);
1982        }
1983
1984        private void loadAllApps() {
1985            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1986
1987            final Callbacks oldCallbacks = mCallbacks.get();
1988            if (oldCallbacks == null) {
1989                // This launcher has exited and nobody bothered to tell us.  Just bail.
1990                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
1991                return;
1992            }
1993
1994            final List<UserHandle> profiles = mUserManager.getUserProfiles();
1995
1996            // Clear the list of apps
1997            mBgAllAppsList.clear();
1998            for (UserHandle user : profiles) {
1999                // Query for the set of apps
2000                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2001                final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
2002                if (DEBUG_LOADERS) {
2003                    Log.d(TAG, "getActivityList took "
2004                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2005                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2006                }
2007                // Fail if we don't have any apps
2008                // TODO: Fix this. Only fail for the current user.
2009                if (apps == null || apps.isEmpty()) {
2010                    return;
2011                }
2012                boolean quietMode = mUserManager.isQuietModeEnabled(user);
2013                // Create the ApplicationInfos
2014                for (int i = 0; i < apps.size(); i++) {
2015                    LauncherActivityInfo app = apps.get(i);
2016                    // This builds the icon bitmaps.
2017                    mBgAllAppsList.add(new AppInfo(mContext, app, user, quietMode), app);
2018                }
2019
2020                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2021                if (heuristic != null) {
2022                    final Runnable r = new Runnable() {
2023
2024                        @Override
2025                        public void run() {
2026                            heuristic.processUserApps(apps);
2027                        }
2028                    };
2029                    runOnMainThread(new Runnable() {
2030
2031                        @Override
2032                        public void run() {
2033                            // Check isLoadingWorkspace on the UI thread, as it is updated on
2034                            // the UI thread.
2035                            if (mIsLoadingAndBindingWorkspace) {
2036                                synchronized (mBindCompleteRunnables) {
2037                                    mBindCompleteRunnables.add(r);
2038                                }
2039                            } else {
2040                                runOnWorkerThread(r);
2041                            }
2042                        }
2043                    });
2044                }
2045            }
2046            // Huh? Shouldn't this be inside the Runnable below?
2047            final ArrayList<AppInfo> added = mBgAllAppsList.added;
2048            mBgAllAppsList.added = new ArrayList<AppInfo>();
2049
2050            // Post callback on main thread
2051            mHandler.post(new Runnable() {
2052                public void run() {
2053
2054                    final long bindTime = SystemClock.uptimeMillis();
2055                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2056                    if (callbacks != null) {
2057                        callbacks.bindAllApplications(added);
2058                        if (DEBUG_LOADERS) {
2059                            Log.d(TAG, "bound " + added.size() + " apps in "
2060                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
2061                        }
2062                    } else {
2063                        Log.i(TAG, "not binding apps: no Launcher activity");
2064                    }
2065                }
2066            });
2067            // Cleanup any data stored for a deleted user.
2068            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2069            if (DEBUG_LOADERS) {
2070                Log.d(TAG, "Icons processed in "
2071                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
2072            }
2073        }
2074
2075        private void loadAndBindDeepShortcuts() {
2076            if (DEBUG_LOADERS) {
2077                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
2078            }
2079            if (!mDeepShortcutsLoaded) {
2080                sBgDataModel.deepShortcutMap.clear();
2081                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
2082                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
2083                if (mHasShortcutHostPermission) {
2084                    for (UserHandle user : mUserManager.getUserProfiles()) {
2085                        if (mUserManager.isUserUnlocked(user)) {
2086                            List<ShortcutInfoCompat> shortcuts =
2087                                    shortcutManager.queryForAllShortcuts(user);
2088                            sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
2089                        }
2090                    }
2091                }
2092                synchronized (LoaderTask.this) {
2093                    if (mStopped) {
2094                        return;
2095                    }
2096                    mDeepShortcutsLoaded = true;
2097                }
2098            }
2099            bindDeepShortcuts();
2100        }
2101
2102        public void dumpState() {
2103            synchronized (sBgDataModel) {
2104                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2105                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2106                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2107                Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
2108            }
2109        }
2110    }
2111
2112    public void bindDeepShortcuts() {
2113        final MultiHashMap<ComponentKey, String> shortcutMapCopy =
2114                sBgDataModel.deepShortcutMap.clone();
2115        Runnable r = new Runnable() {
2116            @Override
2117            public void run() {
2118                Callbacks callbacks = getCallback();
2119                if (callbacks != null) {
2120                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
2121                }
2122            }
2123        };
2124        runOnMainThread(r);
2125    }
2126
2127    /**
2128     * Refreshes the cached shortcuts if the shortcut permission has changed.
2129     * Current implementation simply reloads the workspace, but it can be optimized to
2130     * use partial updates similar to {@link UserManagerCompat}
2131     */
2132    public void refreshShortcutsIfRequired() {
2133        if (Utilities.ATLEAST_NOUGAT_MR1) {
2134            sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
2135            sWorker.post(mShortcutPermissionCheckRunnable);
2136        }
2137    }
2138
2139    /**
2140     * Called when the icons for packages have been updated in the icon cache.
2141     */
2142    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
2143        // If any package icon has changed (app was updated while launcher was dead),
2144        // update the corresponding shortcuts.
2145        enqueueModelUpdateTask(new CacheDataUpdatedTask(
2146                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
2147    }
2148
2149    void enqueueModelUpdateTask(BaseModelUpdateTask task) {
2150        task.init(this);
2151        runOnWorkerThread(task);
2152    }
2153
2154    /**
2155     * A task to be executed on the current callbacks on the UI thread.
2156     * If there is no current callbacks, the task is ignored.
2157     */
2158    public interface CallbackTask {
2159
2160        void execute(Callbacks callbacks);
2161    }
2162
2163    /**
2164     * A runnable which changes/updates the data model of the launcher based on certain events.
2165     */
2166    public static abstract class BaseModelUpdateTask implements Runnable {
2167
2168        private LauncherModel mModel;
2169        private DeferredHandler mUiHandler;
2170
2171        /* package private */
2172        void init(LauncherModel model) {
2173            mModel = model;
2174            mUiHandler = mModel.mHandler;
2175        }
2176
2177        @Override
2178        public void run() {
2179            if (!mModel.mHasLoaderCompletedOnce) {
2180                // Loader has not yet run.
2181                return;
2182            }
2183            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
2184        }
2185
2186        /**
2187         * Execute the actual task. Called on the worker thread.
2188         */
2189        public abstract void execute(
2190                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
2191
2192        /**
2193         * Schedules a {@param task} to be executed on the current callbacks.
2194         */
2195        public final void scheduleCallbackTask(final CallbackTask task) {
2196            final Callbacks callbacks = mModel.getCallback();
2197            mUiHandler.post(new Runnable() {
2198                public void run() {
2199                    Callbacks cb = mModel.getCallback();
2200                    if (callbacks == cb && cb != null) {
2201                        task.execute(callbacks);
2202                    }
2203                }
2204            });
2205        }
2206    }
2207
2208    public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
2209        updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
2210            @Override
2211            public ShortcutInfo get() {
2212                si.updateFromDeepShortcutInfo(info, mApp.getContext());
2213                si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
2214                return si;
2215            }
2216        });
2217    }
2218
2219    /**
2220     * Utility method to update a shortcut on the background thread.
2221     */
2222    public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
2223        enqueueModelUpdateTask(new ExtendedModelTask() {
2224            @Override
2225            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
2226                ShortcutInfo info = shortcutProvider.get();
2227                ArrayList<ShortcutInfo> update = new ArrayList<>();
2228                update.add(info);
2229                bindUpdatedShortcuts(update, info.user);
2230            }
2231        });
2232    }
2233
2234    private void bindWidgetsModel(final Callbacks callbacks) {
2235        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
2236                = mBgWidgetsModel.getWidgetsMap().clone();
2237        mHandler.post(new Runnable() {
2238            @Override
2239            public void run() {
2240                Callbacks cb = getCallback();
2241                if (callbacks == cb && cb != null) {
2242                    callbacks.bindAllWidgets(widgets);
2243                }
2244            }
2245        });
2246    }
2247
2248    public void refreshAndBindWidgetsAndShortcuts(
2249            final Callbacks callbacks, final boolean bindFirst) {
2250        runOnWorkerThread(new Runnable() {
2251            @Override
2252            public void run() {
2253                if (bindFirst && !mBgWidgetsModel.isEmpty()) {
2254                    bindWidgetsModel(callbacks);
2255                }
2256                ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext());
2257                bindWidgetsModel(callbacks);
2258
2259                // update the Widget entries inside DB on the worker thread.
2260                mApp.getWidgetCache().removeObsoletePreviews(allWidgets);
2261            }
2262        });
2263    }
2264
2265    static boolean isValidProvider(AppWidgetProviderInfo provider) {
2266        return (provider != null) && (provider.provider != null)
2267                && (provider.provider.getPackageName() != null);
2268    }
2269
2270    public void dumpState() {
2271        Log.d(TAG, "mCallbacks=" + mCallbacks);
2272        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
2273        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
2274        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
2275        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
2276        if (mLoaderTask != null) {
2277            mLoaderTask.dumpState();
2278        } else {
2279            Log.d(TAG, "mLoaderTask=null");
2280        }
2281    }
2282
2283    public Callbacks getCallback() {
2284        return mCallbacks != null ? mCallbacks.get() : null;
2285    }
2286
2287    /**
2288     * @return {@link FolderInfo} if its already loaded.
2289     */
2290    public FolderInfo findFolderById(Long folderId) {
2291        synchronized (sBgDataModel) {
2292            return sBgDataModel.folders.get(folderId);
2293        }
2294    }
2295
2296    @Thunk class DeferredMainThreadExecutor implements Executor {
2297
2298        @Override
2299        public void execute(Runnable command) {
2300            runOnMainThread(command);
2301        }
2302    }
2303
2304    /**
2305     * @return the looper for the worker thread which can be used to start background tasks.
2306     */
2307    public static Looper getWorkerLooper() {
2308        return sWorkerThread.getLooper();
2309    }
2310}
2311