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