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