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