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