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