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