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 
17 package com.android.launcher2;
18 
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetManager;
21 import android.appwidget.AppWidgetProviderInfo;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentProviderClient;
25 import android.content.ContentResolver;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.Intent.ShortcutIconResource;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.LauncherActivityInfo;
32 import android.content.pm.LauncherApps;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ResolveInfo;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.database.Cursor;
38 import android.graphics.Bitmap;
39 import android.graphics.BitmapFactory;
40 import android.net.Uri;
41 import android.os.Environment;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.os.Parcelable;
45 import android.os.Process;
46 import android.os.RemoteException;
47 import android.os.SystemClock;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.util.Log;
51 
52 import com.android.launcher.R;
53 import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
54 
55 import java.lang.ref.WeakReference;
56 import java.net.URISyntaxException;
57 import java.text.Collator;
58 import java.util.Arrays;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.Comparator;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Set;
67 
68 /**
69  * Maintains in-memory state of the Launcher. It is expected that there should be only one
70  * LauncherModel object held in a static. Also provide APIs for updating the database state
71  * for the Launcher.
72  */
73 public class LauncherModel extends BroadcastReceiver {
74     static final boolean DEBUG_LOADERS = false;
75     static final String TAG = "Launcher.Model";
76 
77     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
78     private int mBatchSize; // 0 is all apps at once
79     private int mAllAppsLoadDelay; // milliseconds between batches
80 
81     private final boolean mAppsCanBeOnRemoveableStorage;
82 
83     private final LauncherApplication mApp;
84     private final Object mLock = new Object();
85     private DeferredHandler mHandler = new DeferredHandler();
86     private LoaderTask mLoaderTask;
87     private boolean mIsLoaderTaskRunning;
88     private volatile boolean mFlushingWorkerThread;
89 
90     // Specific runnable types that are run on the main thread deferred handler, this allows us to
91     // clear all queued binding runnables when the Launcher activity is destroyed.
92     private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
93     private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
94 
95 
96     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
97     static {
sWorkerThread.start()98         sWorkerThread.start();
99     }
100     private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
101 
102     // We start off with everything not loaded.  After that, we assume that
103     // our monitoring of the package manager provides all updates and we never
104     // need to do a requery.  These are only ever touched from the loader thread.
105     private boolean mWorkspaceLoaded;
106     private boolean mAllAppsLoaded;
107 
108     // When we are loading pages synchronously, we can't just post the binding of items on the side
109     // pages as this delays the rotation process.  Instead, we wait for a callback from the first
110     // draw (in Workspace) to initiate the binding of the remaining side pages.  Any time we start
111     // a normal load, we also clear this set of Runnables.
112     static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
113 
114     private WeakReference<Callbacks> mCallbacks;
115 
116     // < only access in worker thread >
117     private AllAppsList mBgAllAppsList;
118 
119     // The lock that must be acquired before referencing any static bg data structures.  Unlike
120     // other locks, this one can generally be held long-term because we never expect any of these
121     // static data structures to be referenced outside of the worker thread except on the first
122     // load after configuration change.
123     static final Object sBgLock = new Object();
124 
125     // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
126     // LauncherModel to their ids
127     static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
128 
129     // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
130     //       created by LauncherModel that are directly on the home screen (however, no widgets or
131     //       shortcuts within folders).
132     static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
133 
134     // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
135     static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
136         new ArrayList<LauncherAppWidgetInfo>();
137 
138     // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
139     static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
140 
141     // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
142     static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
143     // </ only access in worker thread >
144 
145     private IconCache mIconCache;
146     private Bitmap mDefaultIcon;
147 
148     private static int mCellCountX;
149     private static int mCellCountY;
150 
151     protected int mPreviousConfigMcc;
152 
153     private final LauncherApps mLauncherApps;
154     final UserManager mUserManager;
155     private final LauncherApps.Callback mLauncherAppsCallback;
156 
157     public interface Callbacks {
setLoadOnResume()158         public boolean setLoadOnResume();
getCurrentWorkspaceScreen()159         public int getCurrentWorkspaceScreen();
startBinding()160         public void startBinding();
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)161         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
bindFolders(HashMap<Long,FolderInfo> folders)162         public void bindFolders(HashMap<Long,FolderInfo> folders);
finishBindingItems()163         public void finishBindingItems();
bindAppWidget(LauncherAppWidgetInfo info)164         public void bindAppWidget(LauncherAppWidgetInfo info);
bindAllApplications(ArrayList<ApplicationInfo> apps)165         public void bindAllApplications(ArrayList<ApplicationInfo> apps);
bindAppsAdded(ArrayList<ApplicationInfo> apps)166         public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
bindAppsUpdated(ArrayList<ApplicationInfo> apps)167         public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<ApplicationInfo> appInfos, boolean matchPackageNamesOnly, UserHandle user)168         public void bindComponentsRemoved(ArrayList<String> packageNames,
169                 ArrayList<ApplicationInfo> appInfos,
170                 boolean matchPackageNamesOnly, UserHandle user);
bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts)171         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
isAllAppsVisible()172         public boolean isAllAppsVisible();
isAllAppsButtonRank(int rank)173         public boolean isAllAppsButtonRank(int rank);
bindSearchablesChanged()174         public void bindSearchablesChanged();
onPageBoundSynchronously(int page)175         public void onPageBoundSynchronously(int page);
176     }
177 
LauncherModel(LauncherApplication app, IconCache iconCache)178     LauncherModel(LauncherApplication app, IconCache iconCache) {
179         mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
180         mApp = app;
181         mBgAllAppsList = new AllAppsList(iconCache);
182         mIconCache = iconCache;
183 
184         mDefaultIcon = Utilities.createIconBitmap(
185                 mIconCache.getFullResDefaultActivityIcon(), app);
186 
187         final Resources res = app.getResources();
188         mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay);
189         mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize);
190         Configuration config = res.getConfiguration();
191         mPreviousConfigMcc = config.mcc;
192         mLauncherApps = (LauncherApps) app.getSystemService(Context.LAUNCHER_APPS_SERVICE);
193         mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
194         mLauncherAppsCallback = new LauncherAppsCallback();
195     }
196 
197     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
198      * posted on the main thread handler. */
runOnMainThread(Runnable r)199     private void runOnMainThread(Runnable r) {
200         runOnMainThread(r, 0);
201     }
202 
runOnMainThread(Runnable r, int type)203     private void runOnMainThread(Runnable r, int type) {
204         if (sWorkerThread.getThreadId() == Process.myTid()) {
205             // If we are on the worker thread, post onto the main handler
206             mHandler.post(r);
207         } else {
208             r.run();
209         }
210     }
211 
212     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
213      * posted on the worker thread handler. */
runOnWorkerThread(Runnable r)214     private static void runOnWorkerThread(Runnable r) {
215         if (sWorkerThread.getThreadId() == Process.myTid()) {
216             r.run();
217         } else {
218             // If we are not on the worker thread, then post to the worker handler
219             sWorker.post(r);
220         }
221     }
222 
getFallbackIcon()223     public Bitmap getFallbackIcon() {
224         return Bitmap.createBitmap(mDefaultIcon);
225     }
226 
unbindItemInfosAndClearQueuedBindRunnables()227     public void unbindItemInfosAndClearQueuedBindRunnables() {
228         if (sWorkerThread.getThreadId() == Process.myTid()) {
229             throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
230                     "main thread");
231         }
232 
233         // Clear any deferred bind runnables
234         mDeferredBindRunnables.clear();
235         // Remove any queued bind runnables
236         mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
237         // Unbind all the workspace items
238         unbindWorkspaceItemsOnMainThread();
239     }
240 
241     /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
unbindWorkspaceItemsOnMainThread()242     void unbindWorkspaceItemsOnMainThread() {
243         // Ensure that we don't use the same workspace items data structure on the main thread
244         // by making a copy of workspace items first.
245         final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
246         final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
247         synchronized (sBgLock) {
248             tmpWorkspaceItems.addAll(sBgWorkspaceItems);
249             tmpAppWidgets.addAll(sBgAppWidgets);
250         }
251         Runnable r = new Runnable() {
252                 @Override
253                 public void run() {
254                    for (ItemInfo item : tmpWorkspaceItems) {
255                        item.unbind();
256                    }
257                    for (ItemInfo item : tmpAppWidgets) {
258                        item.unbind();
259                    }
260                 }
261             };
262         runOnMainThread(r);
263     }
264 
265     /**
266      * Adds an item to the DB if it was not created previously, or move it to a new
267      * <container, screen, cellX, cellY>
268      */
addOrMoveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY)269     static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
270             int screen, int cellX, int cellY) {
271         if (item.container == ItemInfo.NO_ID) {
272             // From all apps
273             addItemToDatabase(context, item, container, screen, cellX, cellY, false);
274         } else {
275             // From somewhere else
276             moveItemInDatabase(context, item, container, screen, cellX, cellY);
277         }
278     }
279 
checkItemInfoLocked( final long itemId, final ItemInfo item, StackTraceElement[] stackTrace)280     static void checkItemInfoLocked(
281             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
282         ItemInfo modelItem = sBgItemsIdMap.get(itemId);
283         if (modelItem != null && item != modelItem) {
284             // check all the data is consistent
285             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
286                 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
287                 ShortcutInfo shortcut = (ShortcutInfo) item;
288                 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
289                         modelShortcut.intent.filterEquals(shortcut.intent) &&
290                         modelShortcut.id == shortcut.id &&
291                         modelShortcut.itemType == shortcut.itemType &&
292                         modelShortcut.container == shortcut.container &&
293                         modelShortcut.screen == shortcut.screen &&
294                         modelShortcut.cellX == shortcut.cellX &&
295                         modelShortcut.cellY == shortcut.cellY &&
296                         modelShortcut.spanX == shortcut.spanX &&
297                         modelShortcut.spanY == shortcut.spanY &&
298                         ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
299                         (modelShortcut.dropPos != null &&
300                                 shortcut.dropPos != null &&
301                                 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
302                         modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
303                     // For all intents and purposes, this is the same object
304                     return;
305                 }
306             }
307 
308             // the modelItem needs to match up perfectly with item if our model is
309             // to be consistent with the database-- for now, just require
310             // modelItem == item or the equality check above
311             String msg = "item: " + ((item != null) ? item.toString() : "null") +
312                     "modelItem: " +
313                     ((modelItem != null) ? modelItem.toString() : "null") +
314                     "Error: ItemInfo passed to checkItemInfo doesn't match original";
315             RuntimeException e = new RuntimeException(msg);
316             if (stackTrace != null) {
317                 e.setStackTrace(stackTrace);
318             }
319             throw e;
320         }
321     }
322 
checkItemInfo(final ItemInfo item)323     static void checkItemInfo(final ItemInfo item) {
324         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
325         final long itemId = item.id;
326         Runnable r = new Runnable() {
327             public void run() {
328                 synchronized (sBgLock) {
329                     checkItemInfoLocked(itemId, item, stackTrace);
330                 }
331             }
332         };
333         runOnWorkerThread(r);
334     }
335 
updateItemInDatabaseHelper(Context context, final ContentValues values, final ItemInfo item, final String callingFunction)336     static void updateItemInDatabaseHelper(Context context, final ContentValues values,
337             final ItemInfo item, final String callingFunction) {
338         final long itemId = item.id;
339         final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
340         final ContentResolver cr = context.getContentResolver();
341 
342         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
343         Runnable r = new Runnable() {
344             public void run() {
345                 cr.update(uri, values, null, null);
346 
347                 // Lock on mBgLock *after* the db operation
348                 synchronized (sBgLock) {
349                     checkItemInfoLocked(itemId, item, stackTrace);
350 
351                     if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
352                             item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
353                         // Item is in a folder, make sure this folder exists
354                         if (!sBgFolders.containsKey(item.container)) {
355                             // An items container is being set to a that of an item which is not in
356                             // the list of Folders.
357                             String msg = "item: " + item + " container being set to: " +
358                                     item.container + ", not in the list of folders";
359                             Log.e(TAG, msg);
360                             Launcher.dumpDebugLogsToConsole();
361                         }
362                     }
363 
364                     // Items are added/removed from the corresponding FolderInfo elsewhere, such
365                     // as in Workspace.onDrop. Here, we just add/remove them from the list of items
366                     // that are on the desktop, as appropriate
367                     ItemInfo modelItem = sBgItemsIdMap.get(itemId);
368                     if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
369                             modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
370                         switch (modelItem.itemType) {
371                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
372                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
373                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
374                                 if (!sBgWorkspaceItems.contains(modelItem)) {
375                                     sBgWorkspaceItems.add(modelItem);
376                                 }
377                                 break;
378                             default:
379                                 break;
380                         }
381                     } else {
382                         sBgWorkspaceItems.remove(modelItem);
383                     }
384                 }
385             }
386         };
387         runOnWorkerThread(r);
388     }
389 
flushWorkerThread()390     public void flushWorkerThread() {
391         mFlushingWorkerThread = true;
392         Runnable waiter = new Runnable() {
393                 public void run() {
394                     synchronized (this) {
395                         notifyAll();
396                         mFlushingWorkerThread = false;
397                     }
398                 }
399             };
400 
401         synchronized(waiter) {
402             runOnWorkerThread(waiter);
403             if (mLoaderTask != null) {
404                 synchronized(mLoaderTask) {
405                     mLoaderTask.notify();
406                 }
407             }
408             boolean success = false;
409             while (!success) {
410                 try {
411                     waiter.wait();
412                     success = true;
413                 } catch (InterruptedException e) {
414                 }
415             }
416         }
417     }
418 
419     /**
420      * Move an item in the DB to a new <container, screen, cellX, cellY>
421      */
moveItemInDatabase(Context context, final ItemInfo item, final long container, final int screen, final int cellX, final int cellY)422     static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
423             final int screen, final int cellX, final int cellY) {
424         String transaction = "DbDebug    Modify item (" + item.title + ") in db, id: " + item.id +
425                 " (" + item.container + ", " + item.screen + ", " + item.cellX + ", " + item.cellY +
426                 ") --> " + "(" + container + ", " + screen + ", " + cellX + ", " + cellY + ")";
427         Launcher.sDumpLogs.add(transaction);
428         Log.d(TAG, transaction);
429         item.container = container;
430         item.cellX = cellX;
431         item.cellY = cellY;
432 
433         // We store hotseat items in canonical form which is this orientation invariant position
434         // in the hotseat
435         if (context instanceof Launcher && screen < 0 &&
436                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
437             item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
438         } else {
439             item.screen = screen;
440         }
441 
442         final ContentValues values = new ContentValues();
443         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
444         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
445         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
446         values.put(LauncherSettings.Favorites.SCREEN, item.screen);
447 
448         updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
449     }
450 
451     /**
452      * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
453      */
modifyItemInDatabase(Context context, final ItemInfo item, final long container, final int screen, final int cellX, final int cellY, final int spanX, final int spanY)454     static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
455             final int screen, final int cellX, final int cellY, final int spanX, final int spanY) {
456         String transaction = "DbDebug    Modify item (" + item.title + ") in db, id: " + item.id +
457                 " (" + item.container + ", " + item.screen + ", " + item.cellX + ", " + item.cellY +
458                 ") --> " + "(" + container + ", " + screen + ", " + cellX + ", " + cellY + ")";
459         Launcher.sDumpLogs.add(transaction);
460         Log.d(TAG, transaction);
461         item.cellX = cellX;
462         item.cellY = cellY;
463         item.spanX = spanX;
464         item.spanY = spanY;
465 
466         // We store hotseat items in canonical form which is this orientation invariant position
467         // in the hotseat
468         if (context instanceof Launcher && screen < 0 &&
469                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
470             item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
471         } else {
472             item.screen = screen;
473         }
474 
475         final ContentValues values = new ContentValues();
476         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
477         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
478         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
479         values.put(LauncherSettings.Favorites.SPANX, item.spanX);
480         values.put(LauncherSettings.Favorites.SPANY, item.spanY);
481         values.put(LauncherSettings.Favorites.SCREEN, item.screen);
482 
483         updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
484     }
485 
486     /**
487      * Update an item to the database in a specified container.
488      */
updateItemInDatabase(Context context, final ItemInfo item)489     static void updateItemInDatabase(Context context, final ItemInfo item) {
490         final ContentValues values = new ContentValues();
491         item.onAddToDatabase(context, values);
492         item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
493         updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
494     }
495 
496     /**
497      * Returns true if the shortcuts already exists in the database.
498      * we identify a shortcut by its title and intent.
499      */
shortcutExists(Context context, String title, Intent intent)500     static boolean shortcutExists(Context context, String title, Intent intent) {
501         final ContentResolver cr = context.getContentResolver();
502         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
503             new String[] { "title", "intent" }, "title=? and intent=?",
504             new String[] { title, intent.toUri(0) }, null);
505         boolean result = false;
506         try {
507             result = c.moveToFirst();
508         } finally {
509             c.close();
510         }
511         return result;
512     }
513 
514     /**
515      * Returns an ItemInfo array containing all the items in the LauncherModel.
516      * The ItemInfo.id is not set through this function.
517      */
getItemsInLocalCoordinates(Context context)518     static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
519         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
520         final ContentResolver cr = context.getContentResolver();
521         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
522                 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
523                 LauncherSettings.Favorites.SCREEN,
524                 LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
525                 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY,
526                 LauncherSettings.Favorites.PROFILE_ID }, null, null, null);
527 
528         final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
529         final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
530         final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
531         final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
532         final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
533         final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
534         final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
535         final int profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
536         final UserManager um = ((UserManager) context.getSystemService(Context.USER_SERVICE));
537         try {
538             while (c.moveToNext()) {
539                 ItemInfo item = new ItemInfo();
540                 item.cellX = c.getInt(cellXIndex);
541                 item.cellY = c.getInt(cellYIndex);
542                 item.spanX = c.getInt(spanXIndex);
543                 item.spanY = c.getInt(spanYIndex);
544                 item.container = c.getInt(containerIndex);
545                 item.itemType = c.getInt(itemTypeIndex);
546                 item.screen = c.getInt(screenIndex);
547                 int serialNumber = c.getInt(profileIdIndex);
548                 item.user = um.getUserForSerialNumber(serialNumber);
549                 // If the user no longer exists, skip this item
550                 if (item.user != null) {
551                     items.add(item);
552                 }
553             }
554         } catch (Exception e) {
555             items.clear();
556         } finally {
557             c.close();
558         }
559 
560         return items;
561     }
562 
563     /**
564      * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
565      */
getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id)566     FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
567         final ContentResolver cr = context.getContentResolver();
568         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
569                 "_id=? and (itemType=? or itemType=?)",
570                 new String[] { String.valueOf(id),
571                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
572 
573         try {
574             if (c.moveToFirst()) {
575                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
576                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
577                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
578                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
579                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
580                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
581 
582                 FolderInfo folderInfo = null;
583                 switch (c.getInt(itemTypeIndex)) {
584                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
585                         folderInfo = findOrMakeFolder(folderList, id);
586                         break;
587                 }
588 
589                 folderInfo.title = c.getString(titleIndex);
590                 folderInfo.id = id;
591                 folderInfo.container = c.getInt(containerIndex);
592                 folderInfo.screen = c.getInt(screenIndex);
593                 folderInfo.cellX = c.getInt(cellXIndex);
594                 folderInfo.cellY = c.getInt(cellYIndex);
595 
596                 return folderInfo;
597             }
598         } finally {
599             c.close();
600         }
601 
602         return null;
603     }
604 
605     /**
606      * Add an item to the database in a specified container. Sets the container, screen, cellX and
607      * cellY fields of the item. Also assigns an ID to the item.
608      */
addItemToDatabase(Context context, final ItemInfo item, final long container, final int screen, final int cellX, final int cellY, final boolean notify)609     static void addItemToDatabase(Context context, final ItemInfo item, final long container,
610             final int screen, final int cellX, final int cellY, final boolean notify) {
611         item.container = container;
612         item.cellX = cellX;
613         item.cellY = cellY;
614         // We store hotseat items in canonical form which is this orientation invariant position
615         // in the hotseat
616         if (context instanceof Launcher && screen < 0 &&
617                 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
618             item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
619         } else {
620             item.screen = screen;
621         }
622 
623         final ContentValues values = new ContentValues();
624         final ContentResolver cr = context.getContentResolver();
625         item.onAddToDatabase(context, values);
626 
627         LauncherApplication app = (LauncherApplication) context.getApplicationContext();
628         item.id = app.getLauncherProvider().generateNewId();
629         values.put(LauncherSettings.Favorites._ID, item.id);
630         item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
631 
632         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
633         Runnable r = new Runnable() {
634             public void run() {
635                 String transaction = "DbDebug    Add item (" + item.title + ") to db, id: "
636                         + item.id + " (" + container + ", " + screen + ", " + cellX + ", "
637                         + cellY + ")";
638                 Launcher.sDumpLogs.add(transaction);
639                 Log.d(TAG, transaction);
640 
641                 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
642                         LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
643 
644                 // Lock on mBgLock *after* the db operation
645                 synchronized (sBgLock) {
646                     checkItemInfoLocked(item.id, item, stackTrace);
647                     sBgItemsIdMap.put(item.id, item);
648                     switch (item.itemType) {
649                         case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
650                             sBgFolders.put(item.id, (FolderInfo) item);
651                             // Fall through
652                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
653                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
654                             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
655                                     item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
656                                 sBgWorkspaceItems.add(item);
657                             } else {
658                                 if (!sBgFolders.containsKey(item.container)) {
659                                     // Adding an item to a folder that doesn't exist.
660                                     String msg = "adding item: " + item + " to a folder that " +
661                                             " doesn't exist";
662                                     Log.e(TAG, msg);
663                                     Launcher.dumpDebugLogsToConsole();
664                                 }
665                             }
666                             break;
667                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
668                             sBgAppWidgets.add((LauncherAppWidgetInfo) item);
669                             break;
670                     }
671                 }
672             }
673         };
674         runOnWorkerThread(r);
675     }
676 
677     /**
678      * Creates a new unique child id, for a given cell span across all layouts.
679      */
getCellLayoutChildId( long container, int screen, int localCellX, int localCellY, int spanX, int spanY)680     static int getCellLayoutChildId(
681             long container, int screen, int localCellX, int localCellY, int spanX, int spanY) {
682         return (((int) container & 0xFF) << 24)
683                 | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
684     }
685 
getCellCountX()686     static int getCellCountX() {
687         return mCellCountX;
688     }
689 
getCellCountY()690     static int getCellCountY() {
691         return mCellCountY;
692     }
693 
694     /**
695      * Updates the model orientation helper to take into account the current layout dimensions
696      * when performing local/canonical coordinate transformations.
697      */
updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount)698     static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) {
699         mCellCountX = shortAxisCellCount;
700         mCellCountY = longAxisCellCount;
701     }
702 
703     /**
704      * Removes the specified item from the database
705      * @param context
706      * @param item
707      */
deleteItemFromDatabase(Context context, final ItemInfo item)708     static void deleteItemFromDatabase(Context context, final ItemInfo item) {
709         final ContentResolver cr = context.getContentResolver();
710         final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
711 
712         Runnable r = new Runnable() {
713             public void run() {
714                 String transaction = "DbDebug    Delete item (" + item.title + ") from db, id: "
715                         + item.id + " (" + item.container + ", " + item.screen + ", " + item.cellX +
716                         ", " + item.cellY + ")";
717                 Launcher.sDumpLogs.add(transaction);
718                 Log.d(TAG, transaction);
719 
720                 cr.delete(uriToDelete, null, null);
721 
722                 // Lock on mBgLock *after* the db operation
723                 synchronized (sBgLock) {
724                     switch (item.itemType) {
725                         case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
726                             sBgFolders.remove(item.id);
727                             for (ItemInfo info: sBgItemsIdMap.values()) {
728                                 if (info.container == item.id) {
729                                     // We are deleting a folder which still contains items that
730                                     // think they are contained by that folder.
731                                     String msg = "deleting a folder (" + item + ") which still " +
732                                             "contains items (" + info + ")";
733                                     Log.e(TAG, msg);
734                                     Launcher.dumpDebugLogsToConsole();
735                                 }
736                             }
737                             sBgWorkspaceItems.remove(item);
738                             break;
739                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
740                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
741                             sBgWorkspaceItems.remove(item);
742                             break;
743                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
744                             sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
745                             break;
746                     }
747                     sBgItemsIdMap.remove(item.id);
748                     sBgDbIconCache.remove(item);
749                 }
750             }
751         };
752         runOnWorkerThread(r);
753     }
754 
755     /**
756      * Remove the contents of the specified folder from the database
757      */
deleteFolderContentsFromDatabase(Context context, final FolderInfo info)758     static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
759         final ContentResolver cr = context.getContentResolver();
760 
761         Runnable r = new Runnable() {
762             public void run() {
763                 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
764                 // Lock on mBgLock *after* the db operation
765                 synchronized (sBgLock) {
766                     sBgItemsIdMap.remove(info.id);
767                     sBgFolders.remove(info.id);
768                     sBgDbIconCache.remove(info);
769                     sBgWorkspaceItems.remove(info);
770                 }
771 
772                 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
773                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
774                 // Lock on mBgLock *after* the db operation
775                 synchronized (sBgLock) {
776                     for (ItemInfo childInfo : info.contents) {
777                         sBgItemsIdMap.remove(childInfo.id);
778                         sBgDbIconCache.remove(childInfo);
779                     }
780                 }
781             }
782         };
783         runOnWorkerThread(r);
784     }
785 
786     /**
787      * Set this as the current Launcher activity object for the loader.
788      */
initialize(Callbacks callbacks)789     public void initialize(Callbacks callbacks) {
790         synchronized (mLock) {
791             mCallbacks = new WeakReference<Callbacks>(callbacks);
792         }
793     }
794 
getLauncherAppsCallback()795     public LauncherApps.Callback getLauncherAppsCallback() {
796         return mLauncherAppsCallback;
797     }
798 
799     private class LauncherAppsCallback extends LauncherApps.Callback {
800         @Override
onPackageChanged(String packageName, UserHandle user)801         public void onPackageChanged(String packageName, UserHandle user) {
802             enqueuePackageUpdated(new PackageUpdatedTask(
803                     PackageUpdatedTask.OP_UPDATE, new String[] { packageName }, user));
804         }
805 
806         @Override
onPackageRemoved(String packageName, UserHandle user)807         public void onPackageRemoved(String packageName, UserHandle user) {
808             enqueuePackageUpdated(new PackageUpdatedTask(
809                     PackageUpdatedTask.OP_REMOVE, new String[] { packageName }, user));
810         }
811 
812         @Override
onPackageAdded(String packageName, UserHandle user)813         public void onPackageAdded(String packageName, UserHandle user) {
814             enqueuePackageUpdated(new PackageUpdatedTask(
815                     PackageUpdatedTask.OP_ADD, new String[] { packageName }, user));
816         }
817 
818         @Override
onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)819         public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
820             if (!replacing) {
821                 enqueuePackageUpdated(new PackageUpdatedTask(
822                         PackageUpdatedTask.OP_ADD, packageNames, user));
823                 if (mAppsCanBeOnRemoveableStorage) {
824                     // Only rebind if we support removable storage. It catches the
825                     // case where apps on the external sd card need to be reloaded.
826                     startLoaderFromBackground();
827                 }
828             } else {
829                 // If we are replacing then just update the packages in the list
830                 enqueuePackageUpdated(new PackageUpdatedTask(
831                         PackageUpdatedTask.OP_UPDATE, packageNames, user));
832             }
833         }
834 
835         @Override
onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)836         public void onPackagesUnavailable(String[] packageNames, UserHandle user,
837                 boolean replacing) {
838             if (!replacing) {
839                 enqueuePackageUpdated(new PackageUpdatedTask(
840                         PackageUpdatedTask.OP_UNAVAILABLE, packageNames, user));
841             }
842         }
843     }
844 
845     /**
846      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
847      * ACTION_PACKAGE_CHANGED.
848      */
849     @Override
onReceive(Context context, Intent intent)850     public void onReceive(Context context, Intent intent) {
851         if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
852 
853         final String action = intent.getAction();
854         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
855             // If we have changed locale we need to clear out the labels in all apps/workspace.
856             forceReload();
857         } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
858              // Check if configuration change was an mcc/mnc change which would affect app resources
859              // and we would need to clear out the labels in all apps/workspace. Same handling as
860              // above for ACTION_LOCALE_CHANGED
861              Configuration currentConfig = context.getResources().getConfiguration();
862              if (mPreviousConfigMcc != currentConfig.mcc) {
863                    Log.d(TAG, "Reload apps on config change. curr_mcc:"
864                        + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
865                    forceReload();
866              }
867              // Update previousConfig
868              mPreviousConfigMcc = currentConfig.mcc;
869         } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
870                    SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
871             if (mCallbacks != null) {
872                 Callbacks callbacks = mCallbacks.get();
873                 if (callbacks != null) {
874                     callbacks.bindSearchablesChanged();
875                 }
876             }
877         }
878     }
879 
forceReload()880     void forceReload() {
881         resetLoadedState(true, true);
882 
883         // Do this here because if the launcher activity is running it will be restarted.
884         // If it's not running startLoaderFromBackground will merely tell it that it needs
885         // to reload.
886         startLoaderFromBackground();
887     }
888 
resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded)889     public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
890         synchronized (mLock) {
891             // Stop any existing loaders first, so they don't set mAllAppsLoaded or
892             // mWorkspaceLoaded to true later
893             stopLoaderLocked();
894             if (resetAllAppsLoaded) mAllAppsLoaded = false;
895             if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
896         }
897     }
898 
899     /**
900      * When the launcher is in the background, it's possible for it to miss paired
901      * configuration changes.  So whenever we trigger the loader from the background
902      * tell the launcher that it needs to re-run the loader when it comes back instead
903      * of doing it now.
904      */
startLoaderFromBackground()905     public void startLoaderFromBackground() {
906         boolean runLoader = false;
907         if (mCallbacks != null) {
908             Callbacks callbacks = mCallbacks.get();
909             if (callbacks != null) {
910                 // Only actually run the loader if they're not paused.
911                 if (!callbacks.setLoadOnResume()) {
912                     runLoader = true;
913                 }
914             }
915         }
916         if (runLoader) {
917             startLoader(false, -1);
918         }
919     }
920 
921     // If there is already a loader task running, tell it to stop.
922     // returns true if isLaunching() was true on the old task
stopLoaderLocked()923     private boolean stopLoaderLocked() {
924         boolean isLaunching = false;
925         LoaderTask oldTask = mLoaderTask;
926         if (oldTask != null) {
927             if (oldTask.isLaunching()) {
928                 isLaunching = true;
929             }
930             oldTask.stopLocked();
931         }
932         return isLaunching;
933     }
934 
startLoader(boolean isLaunching, int synchronousBindPage)935     public void startLoader(boolean isLaunching, int synchronousBindPage) {
936         synchronized (mLock) {
937             if (DEBUG_LOADERS) {
938                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
939             }
940 
941             // Clear any deferred bind-runnables from the synchronized load process
942             // We must do this before any loading/binding is scheduled below.
943             mDeferredBindRunnables.clear();
944 
945             // Don't bother to start the thread if we know it's not going to do anything
946             if (mCallbacks != null && mCallbacks.get() != null) {
947                 // If there is already one running, tell it to stop.
948                 // also, don't downgrade isLaunching if we're already running
949                 isLaunching = isLaunching || stopLoaderLocked();
950                 mLoaderTask = new LoaderTask(mApp, isLaunching);
951                 if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
952                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
953                 } else {
954                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
955                     sWorker.post(mLoaderTask);
956                 }
957             }
958         }
959     }
960 
bindRemainingSynchronousPages()961     void bindRemainingSynchronousPages() {
962         // Post the remaining side pages to be loaded
963         if (!mDeferredBindRunnables.isEmpty()) {
964             for (final Runnable r : mDeferredBindRunnables) {
965                 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
966             }
967             mDeferredBindRunnables.clear();
968         }
969     }
970 
stopLoader()971     public void stopLoader() {
972         synchronized (mLock) {
973             if (mLoaderTask != null) {
974                 mLoaderTask.stopLocked();
975             }
976         }
977     }
978 
isAllAppsLoaded()979     public boolean isAllAppsLoaded() {
980         return mAllAppsLoaded;
981     }
982 
isLoadingWorkspace()983     boolean isLoadingWorkspace() {
984         synchronized (mLock) {
985             if (mLoaderTask != null) {
986                 return mLoaderTask.isLoadingWorkspace();
987             }
988         }
989         return false;
990     }
991 
992     /**
993      * Runnable for the thread that loads the contents of the launcher:
994      *   - workspace icons
995      *   - widgets
996      *   - all apps icons
997      */
998     private class LoaderTask implements Runnable {
999         private Context mContext;
1000         private boolean mIsLaunching;
1001         private boolean mIsLoadingAndBindingWorkspace;
1002         private boolean mStopped;
1003         private boolean mLoadAndBindStepFinished;
1004 
1005         private HashMap<Object, CharSequence> mLabelCache;
1006 
LoaderTask(Context context, boolean isLaunching)1007         LoaderTask(Context context, boolean isLaunching) {
1008             mContext = context;
1009             mIsLaunching = isLaunching;
1010             mLabelCache = new HashMap<Object, CharSequence>();
1011         }
1012 
isLaunching()1013         boolean isLaunching() {
1014             return mIsLaunching;
1015         }
1016 
isLoadingWorkspace()1017         boolean isLoadingWorkspace() {
1018             return mIsLoadingAndBindingWorkspace;
1019         }
1020 
loadAndBindWorkspace()1021         private void loadAndBindWorkspace() {
1022             mIsLoadingAndBindingWorkspace = true;
1023 
1024             // Load the workspace
1025             if (DEBUG_LOADERS) {
1026                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1027             }
1028 
1029             if (!mWorkspaceLoaded) {
1030                 loadWorkspace();
1031                 synchronized (LoaderTask.this) {
1032                     if (mStopped) {
1033                         return;
1034                     }
1035                     mWorkspaceLoaded = true;
1036                 }
1037             }
1038 
1039             // Bind the workspace
1040             bindWorkspace(-1);
1041         }
1042 
waitForIdle()1043         private void waitForIdle() {
1044             // Wait until the either we're stopped or the other threads are done.
1045             // This way we don't start loading all apps until the workspace has settled
1046             // down.
1047             synchronized (LoaderTask.this) {
1048                 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1049 
1050                 mHandler.postIdle(new Runnable() {
1051                         public void run() {
1052                             synchronized (LoaderTask.this) {
1053                                 mLoadAndBindStepFinished = true;
1054                                 if (DEBUG_LOADERS) {
1055                                     Log.d(TAG, "done with previous binding step");
1056                                 }
1057                                 LoaderTask.this.notify();
1058                             }
1059                         }
1060                     });
1061 
1062                 while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
1063                     try {
1064                         // Just in case mFlushingWorkerThread changes but we aren't woken up,
1065                         // wait no longer than 1sec at a time
1066                         this.wait(1000);
1067                     } catch (InterruptedException ex) {
1068                         // Ignore
1069                     }
1070                 }
1071                 if (DEBUG_LOADERS) {
1072                     Log.d(TAG, "waited "
1073                             + (SystemClock.uptimeMillis()-workspaceWaitTime)
1074                             + "ms for previous step to finish binding");
1075                 }
1076             }
1077         }
1078 
runBindSynchronousPage(int synchronousBindPage)1079         void runBindSynchronousPage(int synchronousBindPage) {
1080             if (synchronousBindPage < 0) {
1081                 // Ensure that we have a valid page index to load synchronously
1082                 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1083                         "valid page index");
1084             }
1085             if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1086                 // Ensure that we don't try and bind a specified page when the pages have not been
1087                 // loaded already (we should load everything asynchronously in that case)
1088                 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1089             }
1090             synchronized (mLock) {
1091                 if (mIsLoaderTaskRunning) {
1092                     // Ensure that we are never running the background loading at this point since
1093                     // we also touch the background collections
1094                     throw new RuntimeException("Error! Background loading is already running");
1095                 }
1096             }
1097 
1098             // XXX: Throw an exception if we are already loading (since we touch the worker thread
1099             //      data structures, we can't allow any other thread to touch that data, but because
1100             //      this call is synchronous, we can get away with not locking).
1101 
1102             // The LauncherModel is static in the LauncherApplication and mHandler may have queued
1103             // operations from the previous activity.  We need to ensure that all queued operations
1104             // are executed before any synchronous binding work is done.
1105             mHandler.flush();
1106 
1107             // Divide the set of loaded items into those that we are binding synchronously, and
1108             // everything else that is to be bound normally (asynchronously).
1109             bindWorkspace(synchronousBindPage);
1110             // XXX: For now, continue posting the binding of AllApps as there are other issues that
1111             //      arise from that.
1112             onlyBindAllApps();
1113         }
1114 
run()1115         public void run() {
1116             synchronized (mLock) {
1117                 mIsLoaderTaskRunning = true;
1118             }
1119 
1120             keep_running: {
1121                 // Elevate priority when Home launches for the first time to avoid
1122                 // starving at boot time. Staring at a blank home is not cool.
1123                 synchronized (mLock) {
1124                     if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1125                             (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1126                     Process.setThreadPriority(mIsLaunching
1127                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1128                 }
1129 
1130                 // First step. Load workspace first, this is necessary since adding of apps from
1131                 // managed profile in all apps is deferred until onResume. See http://b/17336902.
1132                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1133                 loadAndBindWorkspace();
1134 
1135                 if (mStopped) {
1136                     break keep_running;
1137                 }
1138 
1139                 // Whew! Hard work done.  Slow us down, and wait until the UI thread has
1140                 // settled down.
1141                 synchronized (mLock) {
1142                     if (mIsLaunching) {
1143                         if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1144                         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1145                     }
1146                 }
1147                 waitForIdle();
1148 
1149                 // Second step. Load all apps.
1150                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1151                 loadAndBindAllApps();
1152 
1153                 // Restore the default thread priority after we are done loading items
1154                 synchronized (mLock) {
1155                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1156                 }
1157             }
1158 
1159 
1160             // Update the saved icons if necessary
1161             if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1162             synchronized (sBgLock) {
1163                 for (Object key : sBgDbIconCache.keySet()) {
1164                     updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1165                 }
1166                 sBgDbIconCache.clear();
1167             }
1168 
1169             // Clear out this reference, otherwise we end up holding it until all of the
1170             // callback runnables are done.
1171             mContext = null;
1172 
1173             synchronized (mLock) {
1174                 // If we are still the last one to be scheduled, remove ourselves.
1175                 if (mLoaderTask == this) {
1176                     mLoaderTask = null;
1177                 }
1178                 mIsLoaderTaskRunning = false;
1179             }
1180         }
1181 
stopLocked()1182         public void stopLocked() {
1183             synchronized (LoaderTask.this) {
1184                 mStopped = true;
1185                 this.notify();
1186             }
1187         }
1188 
1189         /**
1190          * Gets the callbacks object.  If we've been stopped, or if the launcher object
1191          * has somehow been garbage collected, return null instead.  Pass in the Callbacks
1192          * object that was around when the deferred message was scheduled, and if there's
1193          * a new Callbacks object around then also return null.  This will save us from
1194          * calling onto it with data that will be ignored.
1195          */
tryGetCallbacks(Callbacks oldCallbacks)1196         Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1197             synchronized (mLock) {
1198                 if (mStopped) {
1199                     return null;
1200                 }
1201 
1202                 if (mCallbacks == null) {
1203                     return null;
1204                 }
1205 
1206                 final Callbacks callbacks = mCallbacks.get();
1207                 if (callbacks != oldCallbacks) {
1208                     return null;
1209                 }
1210                 if (callbacks == null) {
1211                     Log.w(TAG, "no mCallbacks");
1212                     return null;
1213                 }
1214 
1215                 return callbacks;
1216             }
1217         }
1218 
1219         // check & update map of what's occupied; used to discard overlapping/invalid items
checkItemPlacement(ItemInfo occupied[][][], ItemInfo item)1220         private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) {
1221             int containerIndex = item.screen;
1222             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1223                 // Return early if we detect that an item is under the hotseat button
1224                 if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) {
1225                     return false;
1226                 }
1227 
1228                 // We use the last index to refer to the hotseat and the screen as the rank, so
1229                 // test and update the occupied state accordingly
1230                 if (occupied[Launcher.SCREEN_COUNT][item.screen][0] != null) {
1231                     Log.e(TAG, "Error loading shortcut into hotseat " + item
1232                         + " into position (" + item.screen + ":" + item.cellX + "," + item.cellY
1233                         + ") occupied by " + occupied[Launcher.SCREEN_COUNT][item.screen][0]);
1234                     return false;
1235                 } else {
1236                     occupied[Launcher.SCREEN_COUNT][item.screen][0] = item;
1237                     return true;
1238                 }
1239             } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1240                 // Skip further checking if it is not the hotseat or workspace container
1241                 return true;
1242             }
1243 
1244             // Check if any workspace icons overlap with each other
1245             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1246                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1247                     if (occupied[containerIndex][x][y] != null) {
1248                         Log.e(TAG, "Error loading shortcut " + item
1249                             + " into cell (" + containerIndex + "-" + item.screen + ":"
1250                             + x + "," + y
1251                             + ") occupied by "
1252                             + occupied[containerIndex][x][y]);
1253                         return false;
1254                     }
1255                 }
1256             }
1257             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1258                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1259                     occupied[containerIndex][x][y] = item;
1260                 }
1261             }
1262 
1263             return true;
1264         }
1265 
loadWorkspace()1266         private void loadWorkspace() {
1267             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1268 
1269             final Context context = mContext;
1270             final ContentResolver contentResolver = context.getContentResolver();
1271             final PackageManager manager = context.getPackageManager();
1272             final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
1273             final boolean isSafeMode = manager.isSafeMode();
1274 
1275             // Make sure the default workspace is loaded, if needed
1276             mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0, false);
1277 
1278             synchronized (sBgLock) {
1279                 sBgWorkspaceItems.clear();
1280                 sBgAppWidgets.clear();
1281                 sBgFolders.clear();
1282                 sBgItemsIdMap.clear();
1283                 sBgDbIconCache.clear();
1284 
1285                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1286 
1287                 final Cursor c = contentResolver.query(
1288                         LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
1289 
1290                 // +1 for the hotseat (it can be larger than the workspace)
1291                 // Load workspace in reverse order to ensure that latest items are loaded first (and
1292                 // before any earlier duplicates)
1293                 final ItemInfo occupied[][][] =
1294                         new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
1295 
1296                 try {
1297                     final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1298                     final int intentIndex = c.getColumnIndexOrThrow
1299                             (LauncherSettings.Favorites.INTENT);
1300                     final int titleIndex = c.getColumnIndexOrThrow
1301                             (LauncherSettings.Favorites.TITLE);
1302                     final int iconTypeIndex = c.getColumnIndexOrThrow(
1303                             LauncherSettings.Favorites.ICON_TYPE);
1304                     final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1305                     final int iconPackageIndex = c.getColumnIndexOrThrow(
1306                             LauncherSettings.Favorites.ICON_PACKAGE);
1307                     final int iconResourceIndex = c.getColumnIndexOrThrow(
1308                             LauncherSettings.Favorites.ICON_RESOURCE);
1309                     final int containerIndex = c.getColumnIndexOrThrow(
1310                             LauncherSettings.Favorites.CONTAINER);
1311                     final int itemTypeIndex = c.getColumnIndexOrThrow(
1312                             LauncherSettings.Favorites.ITEM_TYPE);
1313                     final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1314                             LauncherSettings.Favorites.APPWIDGET_ID);
1315                     final int screenIndex = c.getColumnIndexOrThrow(
1316                             LauncherSettings.Favorites.SCREEN);
1317                     final int cellXIndex = c.getColumnIndexOrThrow
1318                             (LauncherSettings.Favorites.CELLX);
1319                     final int cellYIndex = c.getColumnIndexOrThrow
1320                             (LauncherSettings.Favorites.CELLY);
1321                     final int spanXIndex = c.getColumnIndexOrThrow
1322                             (LauncherSettings.Favorites.SPANX);
1323                     final int spanYIndex = c.getColumnIndexOrThrow(
1324                             LauncherSettings.Favorites.SPANY);
1325                     final int profileIdIndex = c.getColumnIndexOrThrow(
1326                             LauncherSettings.Favorites.PROFILE_ID);
1327                     //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1328                     //final int displayModeIndex = c.getColumnIndexOrThrow(
1329                     //        LauncherSettings.Favorites.DISPLAY_MODE);
1330 
1331                     ShortcutInfo info;
1332                     String intentDescription;
1333                     LauncherAppWidgetInfo appWidgetInfo;
1334                     int container;
1335                     long id;
1336                     Intent intent;
1337                     UserHandle user;
1338 
1339                     while (!mStopped && c.moveToNext()) {
1340                         try {
1341                             int itemType = c.getInt(itemTypeIndex);
1342 
1343                             switch (itemType) {
1344                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1345                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1346                                 intentDescription = c.getString(intentIndex);
1347                                 int serialNumber = c.getInt(profileIdIndex);
1348                                 user = mUserManager.getUserForSerialNumber(serialNumber);
1349                                 // If the user doesn't exist anymore, skip.
1350                                 if (user == null) {
1351                                     itemsToRemove.add(c.getLong(idIndex));
1352                                     continue;
1353                                 }
1354                                 try {
1355                                     intent = Intent.parseUri(intentDescription, 0);
1356                                 } catch (URISyntaxException e) {
1357                                     continue;
1358                                 }
1359 
1360                                 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1361                                         info = getShortcutInfo(
1362                                                 manager, intent, user, context, c, iconIndex,
1363                                             titleIndex, mLabelCache);
1364                                 } else {
1365                                     info = getShortcutInfo(c, context, iconTypeIndex,
1366                                             iconPackageIndex, iconResourceIndex, iconIndex,
1367                                             titleIndex);
1368 
1369                                     // App shortcuts that used to be automatically added to Launcher
1370                                     // didn't always have the correct intent flags set, so do that
1371                                     // here
1372                                     if (intent.getAction() != null &&
1373                                         intent.getCategories() != null &&
1374                                         intent.getAction().equals(Intent.ACTION_MAIN) &&
1375                                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1376                                         intent.addFlags(
1377                                             Intent.FLAG_ACTIVITY_NEW_TASK |
1378                                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1379                                     }
1380                                 }
1381 
1382                                 if (info != null) {
1383                                     info.intent = intent;
1384                                     info.id = c.getLong(idIndex);
1385                                     container = c.getInt(containerIndex);
1386                                     info.container = container;
1387                                     info.screen = c.getInt(screenIndex);
1388                                     info.cellX = c.getInt(cellXIndex);
1389                                     info.cellY = c.getInt(cellYIndex);
1390                                     info.intent.putExtra(ItemInfo.EXTRA_PROFILE, info.user);
1391 
1392                                     // check & update map of what's occupied
1393                                     if (!checkItemPlacement(occupied, info)) {
1394                                         break;
1395                                     }
1396 
1397                                     switch (container) {
1398                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1399                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1400                                         sBgWorkspaceItems.add(info);
1401                                         break;
1402                                     default:
1403                                         // Item is in a user folder
1404                                         FolderInfo folderInfo =
1405                                                 findOrMakeFolder(sBgFolders, container);
1406                                         folderInfo.add(info);
1407                                         break;
1408                                     }
1409                                     sBgItemsIdMap.put(info.id, info);
1410 
1411                                     // now that we've loaded everthing re-save it with the
1412                                     // icon in case it disappears somehow.
1413                                     queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
1414                                 } else {
1415                                     // Failed to load the shortcut, probably because the
1416                                     // activity manager couldn't resolve it (maybe the app
1417                                     // was uninstalled), or the db row was somehow screwed up.
1418                                     // Delete it.
1419                                     id = c.getLong(idIndex);
1420                                     Log.e(TAG, "Error loading shortcut " + id + ", removing it");
1421                                     contentResolver.delete(LauncherSettings.Favorites.getContentUri(
1422                                                 id, false), null, null);
1423                                 }
1424                                 break;
1425 
1426                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1427                                 id = c.getLong(idIndex);
1428                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
1429 
1430                                 folderInfo.title = c.getString(titleIndex);
1431                                 folderInfo.id = id;
1432                                 container = c.getInt(containerIndex);
1433                                 folderInfo.container = container;
1434                                 folderInfo.screen = c.getInt(screenIndex);
1435                                 folderInfo.cellX = c.getInt(cellXIndex);
1436                                 folderInfo.cellY = c.getInt(cellYIndex);
1437 
1438                                 // check & update map of what's occupied
1439                                 if (!checkItemPlacement(occupied, folderInfo)) {
1440                                     break;
1441                                 }
1442                                 switch (container) {
1443                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1444                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1445                                         sBgWorkspaceItems.add(folderInfo);
1446                                         break;
1447                                 }
1448 
1449                                 sBgItemsIdMap.put(folderInfo.id, folderInfo);
1450                                 sBgFolders.put(folderInfo.id, folderInfo);
1451                                 break;
1452 
1453                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1454                                 // Read all Launcher-specific widget details
1455                                 int appWidgetId = c.getInt(appWidgetIdIndex);
1456                                 id = c.getLong(idIndex);
1457 
1458                                 final AppWidgetProviderInfo provider =
1459                                         widgets.getAppWidgetInfo(appWidgetId);
1460 
1461                                 if (!isSafeMode && (provider == null || provider.provider == null ||
1462                                         provider.provider.getPackageName() == null)) {
1463                                     String log = "Deleting widget that isn't installed anymore: id="
1464                                         + id + " appWidgetId=" + appWidgetId;
1465                                     Log.e(TAG, log);
1466                                     Launcher.sDumpLogs.add(log);
1467                                     itemsToRemove.add(id);
1468                                 } else {
1469                                     appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1470                                             provider.provider);
1471                                     appWidgetInfo.id = id;
1472                                     appWidgetInfo.screen = c.getInt(screenIndex);
1473                                     appWidgetInfo.cellX = c.getInt(cellXIndex);
1474                                     appWidgetInfo.cellY = c.getInt(cellYIndex);
1475                                     appWidgetInfo.spanX = c.getInt(spanXIndex);
1476                                     appWidgetInfo.spanY = c.getInt(spanYIndex);
1477                                     int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
1478                                     appWidgetInfo.minSpanX = minSpan[0];
1479                                     appWidgetInfo.minSpanY = minSpan[1];
1480 
1481                                     container = c.getInt(containerIndex);
1482                                     if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1483                                         container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1484                                         Log.e(TAG, "Widget found where container != " +
1485                                             "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1486                                         continue;
1487                                     }
1488                                     appWidgetInfo.container = c.getInt(containerIndex);
1489 
1490                                     // check & update map of what's occupied
1491                                     if (!checkItemPlacement(occupied, appWidgetInfo)) {
1492                                         break;
1493                                     }
1494                                     sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
1495                                     sBgAppWidgets.add(appWidgetInfo);
1496                                 }
1497                                 break;
1498                             }
1499                         } catch (Exception e) {
1500                             Log.w(TAG, "Desktop items loading interrupted:", e);
1501                         }
1502                     }
1503                 } finally {
1504                     c.close();
1505                 }
1506 
1507                 if (itemsToRemove.size() > 0) {
1508                     ContentProviderClient client = contentResolver.acquireContentProviderClient(
1509                                     LauncherSettings.Favorites.CONTENT_URI);
1510                     // Remove dead items
1511                     for (long id : itemsToRemove) {
1512                         if (DEBUG_LOADERS) {
1513                             Log.d(TAG, "Removed id = " + id);
1514                         }
1515                         // Don't notify content observers
1516                         try {
1517                             client.delete(LauncherSettings.Favorites.getContentUri(id, false),
1518                                     null, null);
1519                         } catch (RemoteException e) {
1520                             Log.w(TAG, "Could not remove id = " + id);
1521                         }
1522                     }
1523                 }
1524 
1525                 if (DEBUG_LOADERS) {
1526                     Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
1527                     Log.d(TAG, "workspace layout: ");
1528                     for (int y = 0; y < mCellCountY; y++) {
1529                         String line = "";
1530                         for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
1531                             if (s > 0) {
1532                                 line += " | ";
1533                             }
1534                             for (int x = 0; x < mCellCountX; x++) {
1535                                 line += ((occupied[s][x][y] != null) ? "#" : ".");
1536                             }
1537                         }
1538                         Log.d(TAG, "[ " + line + " ]");
1539                     }
1540                 }
1541             }
1542         }
1543 
1544         /** Filters the set of items who are directly or indirectly (via another container) on the
1545          * specified screen. */
filterCurrentWorkspaceItems(int currentScreen, ArrayList<ItemInfo> allWorkspaceItems, ArrayList<ItemInfo> currentScreenItems, ArrayList<ItemInfo> otherScreenItems)1546         private void filterCurrentWorkspaceItems(int currentScreen,
1547                 ArrayList<ItemInfo> allWorkspaceItems,
1548                 ArrayList<ItemInfo> currentScreenItems,
1549                 ArrayList<ItemInfo> otherScreenItems) {
1550             // Purge any null ItemInfos
1551             Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
1552             while (iter.hasNext()) {
1553                 ItemInfo i = iter.next();
1554                 if (i == null) {
1555                     iter.remove();
1556                 }
1557             }
1558 
1559             // If we aren't filtering on a screen, then the set of items to load is the full set of
1560             // items given.
1561             if (currentScreen < 0) {
1562                 currentScreenItems.addAll(allWorkspaceItems);
1563             }
1564 
1565             // Order the set of items by their containers first, this allows use to walk through the
1566             // list sequentially, build up a list of containers that are in the specified screen,
1567             // as well as all items in those containers.
1568             Set<Long> itemsOnScreen = new HashSet<Long>();
1569             Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
1570                 @Override
1571                 public int compare(ItemInfo lhs, ItemInfo rhs) {
1572                     return (int) (lhs.container - rhs.container);
1573                 }
1574             });
1575             for (ItemInfo info : allWorkspaceItems) {
1576                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1577                     if (info.screen == currentScreen) {
1578                         currentScreenItems.add(info);
1579                         itemsOnScreen.add(info.id);
1580                     } else {
1581                         otherScreenItems.add(info);
1582                     }
1583                 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1584                     currentScreenItems.add(info);
1585                     itemsOnScreen.add(info.id);
1586                 } else {
1587                     if (itemsOnScreen.contains(info.container)) {
1588                         currentScreenItems.add(info);
1589                         itemsOnScreen.add(info.id);
1590                     } else {
1591                         otherScreenItems.add(info);
1592                     }
1593                 }
1594             }
1595         }
1596 
1597         /** Filters the set of widgets which are on the specified screen. */
filterCurrentAppWidgets(int currentScreen, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, ArrayList<LauncherAppWidgetInfo> otherScreenWidgets)1598         private void filterCurrentAppWidgets(int currentScreen,
1599                 ArrayList<LauncherAppWidgetInfo> appWidgets,
1600                 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
1601                 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
1602             // If we aren't filtering on a screen, then the set of items to load is the full set of
1603             // widgets given.
1604             if (currentScreen < 0) {
1605                 currentScreenWidgets.addAll(appWidgets);
1606             }
1607 
1608             for (LauncherAppWidgetInfo widget : appWidgets) {
1609                 if (widget == null) continue;
1610                 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1611                         widget.screen == currentScreen) {
1612                     currentScreenWidgets.add(widget);
1613                 } else {
1614                     otherScreenWidgets.add(widget);
1615                 }
1616             }
1617         }
1618 
1619         /** Filters the set of folders which are on the specified screen. */
filterCurrentFolders(int currentScreen, HashMap<Long, ItemInfo> itemsIdMap, HashMap<Long, FolderInfo> folders, HashMap<Long, FolderInfo> currentScreenFolders, HashMap<Long, FolderInfo> otherScreenFolders)1620         private void filterCurrentFolders(int currentScreen,
1621                 HashMap<Long, ItemInfo> itemsIdMap,
1622                 HashMap<Long, FolderInfo> folders,
1623                 HashMap<Long, FolderInfo> currentScreenFolders,
1624                 HashMap<Long, FolderInfo> otherScreenFolders) {
1625             // If we aren't filtering on a screen, then the set of items to load is the full set of
1626             // widgets given.
1627             if (currentScreen < 0) {
1628                 currentScreenFolders.putAll(folders);
1629             }
1630 
1631             for (long id : folders.keySet()) {
1632                 ItemInfo info = itemsIdMap.get(id);
1633                 FolderInfo folder = folders.get(id);
1634                 if (info == null || folder == null) continue;
1635                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1636                         info.screen == currentScreen) {
1637                     currentScreenFolders.put(id, folder);
1638                 } else {
1639                     otherScreenFolders.put(id, folder);
1640                 }
1641             }
1642         }
1643 
1644         /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
1645          * right) */
sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)1646         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
1647             // XXX: review this
1648             Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
1649                 @Override
1650                 public int compare(ItemInfo lhs, ItemInfo rhs) {
1651                     int cellCountX = LauncherModel.getCellCountX();
1652                     int cellCountY = LauncherModel.getCellCountY();
1653                     int screenOffset = cellCountX * cellCountY;
1654                     int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
1655                     long lr = (lhs.container * containerOffset + lhs.screen * screenOffset +
1656                             lhs.cellY * cellCountX + lhs.cellX);
1657                     long rr = (rhs.container * containerOffset + rhs.screen * screenOffset +
1658                             rhs.cellY * cellCountX + rhs.cellX);
1659                     return (int) (lr - rr);
1660                 }
1661             });
1662         }
1663 
bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final HashMap<Long, FolderInfo> folders, ArrayList<Runnable> deferredBindRunnables)1664         private void bindWorkspaceItems(final Callbacks oldCallbacks,
1665                 final ArrayList<ItemInfo> workspaceItems,
1666                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
1667                 final HashMap<Long, FolderInfo> folders,
1668                 ArrayList<Runnable> deferredBindRunnables) {
1669 
1670             final boolean postOnMainThread = (deferredBindRunnables != null);
1671 
1672             // Bind the workspace items
1673             int N = workspaceItems.size();
1674             for (int i = 0; i < N; i += ITEMS_CHUNK) {
1675                 final int start = i;
1676                 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1677                 final Runnable r = new Runnable() {
1678                     @Override
1679                     public void run() {
1680                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1681                         if (callbacks != null) {
1682                             callbacks.bindItems(workspaceItems, start, start+chunkSize);
1683                         }
1684                     }
1685                 };
1686                 if (postOnMainThread) {
1687                     deferredBindRunnables.add(r);
1688                 } else {
1689                     runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1690                 }
1691             }
1692 
1693             // Bind the folders
1694             if (!folders.isEmpty()) {
1695                 final Runnable r = new Runnable() {
1696                     public void run() {
1697                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1698                         if (callbacks != null) {
1699                             callbacks.bindFolders(folders);
1700                         }
1701                     }
1702                 };
1703                 if (postOnMainThread) {
1704                     deferredBindRunnables.add(r);
1705                 } else {
1706                     runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1707                 }
1708             }
1709 
1710             // Bind the widgets, one at a time
1711             N = appWidgets.size();
1712             for (int i = 0; i < N; i++) {
1713                 final LauncherAppWidgetInfo widget = appWidgets.get(i);
1714                 final Runnable r = new Runnable() {
1715                     public void run() {
1716                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1717                         if (callbacks != null) {
1718                             callbacks.bindAppWidget(widget);
1719                         }
1720                     }
1721                 };
1722                 if (postOnMainThread) {
1723                     deferredBindRunnables.add(r);
1724                 } else {
1725                     runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1726                 }
1727             }
1728         }
1729 
1730         /**
1731          * Binds all loaded data to actual views on the main thread.
1732          */
bindWorkspace(int synchronizeBindPage)1733         private void bindWorkspace(int synchronizeBindPage) {
1734             final long t = SystemClock.uptimeMillis();
1735             Runnable r;
1736 
1737             // Don't use these two variables in any of the callback runnables.
1738             // Otherwise we hold a reference to them.
1739             final Callbacks oldCallbacks = mCallbacks.get();
1740             if (oldCallbacks == null) {
1741                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1742                 Log.w(TAG, "LoaderTask running with no launcher");
1743                 return;
1744             }
1745 
1746             final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
1747             final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
1748                 oldCallbacks.getCurrentWorkspaceScreen();
1749 
1750             // Load all the items that are on the current page first (and in the process, unbind
1751             // all the existing workspace items before we call startBinding() below.
1752             unbindWorkspaceItemsOnMainThread();
1753             ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
1754             ArrayList<LauncherAppWidgetInfo> appWidgets =
1755                     new ArrayList<LauncherAppWidgetInfo>();
1756             HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
1757             HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
1758             synchronized (sBgLock) {
1759                 workspaceItems.addAll(sBgWorkspaceItems);
1760                 appWidgets.addAll(sBgAppWidgets);
1761                 folders.putAll(sBgFolders);
1762                 itemsIdMap.putAll(sBgItemsIdMap);
1763             }
1764 
1765             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
1766             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
1767             ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
1768                     new ArrayList<LauncherAppWidgetInfo>();
1769             ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
1770                     new ArrayList<LauncherAppWidgetInfo>();
1771             HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
1772             HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
1773 
1774             // Separate the items that are on the current screen, and all the other remaining items
1775             filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
1776                     otherWorkspaceItems);
1777             filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
1778                     otherAppWidgets);
1779             filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
1780                     otherFolders);
1781             sortWorkspaceItemsSpatially(currentWorkspaceItems);
1782             sortWorkspaceItemsSpatially(otherWorkspaceItems);
1783 
1784             // Tell the workspace that we're about to start binding items
1785             r = new Runnable() {
1786                 public void run() {
1787                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1788                     if (callbacks != null) {
1789                         callbacks.startBinding();
1790                     }
1791                 }
1792             };
1793             runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1794 
1795             // Load items on the current page
1796             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
1797                     currentFolders, null);
1798             if (isLoadingSynchronously) {
1799                 r = new Runnable() {
1800                     public void run() {
1801                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1802                         if (callbacks != null) {
1803                             callbacks.onPageBoundSynchronously(currentScreen);
1804                         }
1805                     }
1806                 };
1807                 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1808             }
1809 
1810             // Load all the remaining pages (if we are loading synchronously, we want to defer this
1811             // work until after the first render)
1812             mDeferredBindRunnables.clear();
1813             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
1814                     (isLoadingSynchronously ? mDeferredBindRunnables : null));
1815 
1816             // Tell the workspace that we're done binding items
1817             r = new Runnable() {
1818                 public void run() {
1819                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1820                     if (callbacks != null) {
1821                         callbacks.finishBindingItems();
1822                     }
1823 
1824                     // If we're profiling, ensure this is the last thing in the queue.
1825                     if (DEBUG_LOADERS) {
1826                         Log.d(TAG, "bound workspace in "
1827                             + (SystemClock.uptimeMillis()-t) + "ms");
1828                     }
1829 
1830                     mIsLoadingAndBindingWorkspace = false;
1831                 }
1832             };
1833             if (isLoadingSynchronously) {
1834                 mDeferredBindRunnables.add(r);
1835             } else {
1836                 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
1837             }
1838         }
1839 
loadAndBindAllApps()1840         private void loadAndBindAllApps() {
1841             if (DEBUG_LOADERS) {
1842                 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
1843             }
1844             if (!mAllAppsLoaded) {
1845                 loadAllAppsByBatch();
1846                 synchronized (LoaderTask.this) {
1847                     if (mStopped) {
1848                         return;
1849                     }
1850                     mAllAppsLoaded = true;
1851                 }
1852             } else {
1853                 onlyBindAllApps();
1854             }
1855         }
1856 
onlyBindAllApps()1857         private void onlyBindAllApps() {
1858             final Callbacks oldCallbacks = mCallbacks.get();
1859             if (oldCallbacks == null) {
1860                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1861                 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
1862                 return;
1863             }
1864 
1865             // shallow copy
1866             @SuppressWarnings("unchecked")
1867             final ArrayList<ApplicationInfo> list
1868                     = (ArrayList<ApplicationInfo>) mBgAllAppsList.data.clone();
1869             Runnable r = new Runnable() {
1870                 public void run() {
1871                     final long t = SystemClock.uptimeMillis();
1872                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1873                     if (callbacks != null) {
1874                         callbacks.bindAllApplications(list);
1875                     }
1876                     if (DEBUG_LOADERS) {
1877                         Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1878                                 + (SystemClock.uptimeMillis()-t) + "ms");
1879                     }
1880                 }
1881             };
1882             boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
1883             if (oldCallbacks.isAllAppsVisible() && isRunningOnMainThread) {
1884                 r.run();
1885             } else {
1886                 mHandler.post(r);
1887             }
1888         }
1889 
loadAllAppsByBatch()1890         private void loadAllAppsByBatch() {
1891             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1892 
1893             // Don't use these two variables in any of the callback runnables.
1894             // Otherwise we hold a reference to them.
1895             final Callbacks oldCallbacks = mCallbacks.get();
1896             if (oldCallbacks == null) {
1897                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1898                 Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)");
1899                 return;
1900             }
1901 
1902             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1903             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1904 
1905             final List<UserHandle> profiles = mUserManager.getUserProfiles();
1906 
1907             mBgAllAppsList.clear();
1908             final int profileCount = profiles.size();
1909             for (int p = 0; p < profileCount; p++) {
1910                 UserHandle user = profiles.get(p);
1911                 List<LauncherActivityInfo> apps = null;
1912                 int N = Integer.MAX_VALUE;
1913 
1914                 int startIndex;
1915                 int i = 0;
1916                 int batchSize = -1;
1917                 while (i < N && !mStopped) {
1918                     if (i == 0) {
1919                         final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1920                         apps = mLauncherApps.getActivityList(null, user);
1921                         if (DEBUG_LOADERS) {
1922                             Log.d(TAG, "queryIntentActivities took "
1923                                     + (SystemClock.uptimeMillis()-qiaTime) + "ms");
1924                         }
1925                         if (apps == null) {
1926                             return;
1927                         }
1928                         N = apps.size();
1929                         if (DEBUG_LOADERS) {
1930                             Log.d(TAG, "queryIntentActivities got " + N + " apps");
1931                         }
1932                         if (N == 0) {
1933                             // There are no apps?!?
1934                             return;
1935                         }
1936                         if (mBatchSize == 0) {
1937                             batchSize = N;
1938                         } else {
1939                             batchSize = mBatchSize;
1940                         }
1941 
1942                         final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1943                         Collections.sort(apps,
1944                                 new LauncherModel.ShortcutNameComparator(mLabelCache));
1945                         if (DEBUG_LOADERS) {
1946                             Log.d(TAG, "sort took "
1947                                     + (SystemClock.uptimeMillis()-sortTime) + "ms");
1948                         }
1949                     }
1950 
1951                     final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1952 
1953                     startIndex = i;
1954                     for (int j=0; i<N && j<batchSize; j++) {
1955                         // This builds the icon bitmaps.
1956                         mBgAllAppsList.add(new ApplicationInfo(apps.get(i), user,
1957                                 mIconCache, mLabelCache));
1958                         i++;
1959                     }
1960 
1961                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1962                     final ArrayList<ApplicationInfo> added = mBgAllAppsList.added;
1963                     final boolean firstProfile = p == 0;
1964                     mBgAllAppsList.added = new ArrayList<ApplicationInfo>();
1965                     mHandler.post(new Runnable() {
1966                         public void run() {
1967                             final long t = SystemClock.uptimeMillis();
1968                             if (callbacks != null) {
1969                                 if (firstProfile) {
1970                                     callbacks.bindAllApplications(added);
1971                                 } else {
1972                                     callbacks.bindAppsAdded(added);
1973                                 }
1974                                 if (DEBUG_LOADERS) {
1975                                     Log.d(TAG, "bound " + added.size() + " apps in "
1976                                         + (SystemClock.uptimeMillis() - t) + "ms");
1977                                 }
1978                             } else {
1979                                 Log.i(TAG, "not binding apps: no Launcher activity");
1980                             }
1981                         }
1982                     });
1983 
1984                     if (DEBUG_LOADERS) {
1985                         Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
1986                                 + (SystemClock.uptimeMillis()-t2) + "ms");
1987                     }
1988 
1989                     if (mAllAppsLoadDelay > 0 && i < N) {
1990                         try {
1991                             if (DEBUG_LOADERS) {
1992                                 Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
1993                             }
1994                             Thread.sleep(mAllAppsLoadDelay);
1995                         } catch (InterruptedException exc) { }
1996                     }
1997                 }
1998 
1999                 if (DEBUG_LOADERS) {
2000                     Log.d(TAG, "cached all " + N + " apps in "
2001                             + (SystemClock.uptimeMillis()-t) + "ms"
2002                             + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
2003                 }
2004             }
2005         }
2006 
dumpState()2007         public void dumpState() {
2008             synchronized (sBgLock) {
2009                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2010                 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2011                 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2012                 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2013                 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2014             }
2015         }
2016     }
2017 
enqueuePackageUpdated(PackageUpdatedTask task)2018     void enqueuePackageUpdated(PackageUpdatedTask task) {
2019         sWorker.post(task);
2020     }
2021 
2022     private class PackageUpdatedTask implements Runnable {
2023         int mOp;
2024         String[] mPackages;
2025         UserHandle mUser;
2026 
2027         public static final int OP_NONE = 0;
2028         public static final int OP_ADD = 1;
2029         public static final int OP_UPDATE = 2;
2030         public static final int OP_REMOVE = 3; // uninstlled
2031         public static final int OP_UNAVAILABLE = 4; // external media unmounted
2032 
PackageUpdatedTask(int op, String[] packages, UserHandle user)2033         public PackageUpdatedTask(int op, String[] packages, UserHandle user) {
2034             mOp = op;
2035             mPackages = packages;
2036             mUser = user;
2037         }
2038 
run()2039         public void run() {
2040             final Context context = mApp;
2041 
2042             final String[] packages = mPackages;
2043             final int N = packages.length;
2044             switch (mOp) {
2045                 case OP_ADD:
2046                     for (int i=0; i<N; i++) {
2047                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2048                         mBgAllAppsList.addPackage(context, packages[i], mUser);
2049                     }
2050                     break;
2051                 case OP_UPDATE:
2052                     for (int i=0; i<N; i++) {
2053                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2054                         mBgAllAppsList.updatePackage(context, packages[i], mUser);
2055                         LauncherApplication app =
2056                                 (LauncherApplication) context.getApplicationContext();
2057                         WidgetPreviewLoader.removeFromDb(
2058                                 app.getWidgetPreviewCacheDb(), packages[i]);
2059                     }
2060                     break;
2061                 case OP_REMOVE:
2062                 case OP_UNAVAILABLE:
2063                     for (int i=0; i<N; i++) {
2064                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2065                         mBgAllAppsList.removePackage(packages[i], mUser);
2066                         LauncherApplication app =
2067                                 (LauncherApplication) context.getApplicationContext();
2068                         WidgetPreviewLoader.removeFromDb(
2069                                 app.getWidgetPreviewCacheDb(), packages[i]);
2070                     }
2071                     break;
2072             }
2073 
2074             ArrayList<ApplicationInfo> added = null;
2075             ArrayList<ApplicationInfo> modified = null;
2076             final ArrayList<ApplicationInfo> removedApps = new ArrayList<ApplicationInfo>();
2077 
2078             if (mBgAllAppsList.added.size() > 0) {
2079                 added = new ArrayList<ApplicationInfo>(mBgAllAppsList.added);
2080                 mBgAllAppsList.added.clear();
2081             }
2082             if (mBgAllAppsList.modified.size() > 0) {
2083                 modified = new ArrayList<ApplicationInfo>(mBgAllAppsList.modified);
2084                 mBgAllAppsList.modified.clear();
2085             }
2086             if (mBgAllAppsList.removed.size() > 0) {
2087                 removedApps.addAll(mBgAllAppsList.removed);
2088                 mBgAllAppsList.removed.clear();
2089             }
2090 
2091             final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
2092             if (callbacks == null) {
2093                 Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
2094                 return;
2095             }
2096 
2097             if (added != null) {
2098                 final ArrayList<ApplicationInfo> addedFinal = added;
2099                 mHandler.post(new Runnable() {
2100                     public void run() {
2101                         Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2102                         if (callbacks == cb && cb != null) {
2103                             callbacks.bindAppsAdded(addedFinal);
2104                         }
2105                     }
2106                 });
2107             }
2108             if (modified != null) {
2109                 final ArrayList<ApplicationInfo> modifiedFinal = modified;
2110                 mHandler.post(new Runnable() {
2111                     public void run() {
2112                         Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2113                         if (callbacks == cb && cb != null) {
2114                             callbacks.bindAppsUpdated(modifiedFinal);
2115                         }
2116                     }
2117                 });
2118             }
2119             // If a package has been removed, or an app has been removed as a result of
2120             // an update (for example), make the removed callback.
2121             if (mOp == OP_REMOVE || !removedApps.isEmpty()) {
2122                 final boolean permanent = (mOp == OP_REMOVE);
2123                 final ArrayList<String> removedPackageNames =
2124                         new ArrayList<String>(Arrays.asList(packages));
2125 
2126                 mHandler.post(new Runnable() {
2127                     public void run() {
2128                         Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2129                         if (callbacks == cb && cb != null) {
2130                             callbacks.bindComponentsRemoved(removedPackageNames,
2131                                     removedApps, permanent, mUser);
2132                         }
2133                     }
2134                 });
2135             }
2136 
2137             final ArrayList<Object> widgetsAndShortcuts =
2138                     getSortedWidgetsAndShortcuts(context);
2139             mHandler.post(new Runnable() {
2140                 @Override
2141                 public void run() {
2142                     Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2143                     if (callbacks == cb && cb != null) {
2144                         callbacks.bindPackagesUpdated(widgetsAndShortcuts);
2145                     }
2146                 }
2147             });
2148         }
2149     }
2150 
2151     // Returns a list of ResolveInfos/AppWindowInfos in sorted order
getSortedWidgetsAndShortcuts(Context context)2152     public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
2153         ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
2154 
2155         // Get all user profiles.
2156         AppWidgetManager widgetManager = (AppWidgetManager) context.getSystemService(
2157                 Context.APPWIDGET_SERVICE);
2158         UserManager userManager = (UserManager) context.getSystemService(
2159                 Context.USER_SERVICE);
2160 
2161         List<UserHandle> profiles = userManager.getUserProfiles();
2162 
2163         // Add the widgets for the managed profiles next.
2164         final int profileCount = profiles.size();
2165         for (int i = 0; i < profileCount; i++) {
2166             UserHandle profile = profiles.get(i);
2167             // Add the widget providers for the profile.
2168             List<AppWidgetProviderInfo> providers = widgetManager
2169                     .getInstalledProvidersForProfile(profile);
2170             widgetsAndShortcuts.addAll(providers);
2171         }
2172 
2173         // Add all shortcuts for the user.
2174         PackageManager packageManager = context.getPackageManager();
2175         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2176         widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
2177 
2178         Collections.sort(widgetsAndShortcuts, new LauncherModel
2179                 .WidgetAndShortcutNameComparator(packageManager));
2180 
2181         return widgetsAndShortcuts;
2182     }
2183 
2184     /**
2185      * This is called from the code that adds shortcuts from the intent receiver.  This
2186      * doesn't have a Cursor, but
2187      */
getShortcutInfo(PackageManager manager, Intent intent, UserHandle user, Context context)2188     public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, UserHandle user,
2189             Context context) {
2190         return getShortcutInfo(manager, intent, user, context, null, -1, -1, null);
2191     }
2192 
2193     /**
2194      * Make an ShortcutInfo object for a shortcut that is an application.
2195      *
2196      * If c is not null, then it will be used to fill in missing data like the title and icon.
2197      */
getShortcutInfo(PackageManager manager, Intent intent, UserHandle user, Context context, Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache)2198     public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, UserHandle user,
2199             Context context,
2200             Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
2201         Bitmap icon = null;
2202         final ShortcutInfo info = new ShortcutInfo();
2203         info.user = user;
2204 
2205         ComponentName componentName = intent.getComponent();
2206         if (componentName == null) {
2207             return null;
2208         }
2209 
2210         LauncherActivityInfo lai = mLauncherApps.resolveActivity(intent, user);
2211         if (lai == null) {
2212             return null;
2213         }
2214 
2215         icon = mIconCache.getIcon(componentName, lai, labelCache);
2216         // the db
2217         if (icon == null) {
2218             if (c != null) {
2219                 icon = getIconFromCursor(c, iconIndex, context);
2220             }
2221         }
2222         // the fallback icon
2223         if (icon == null) {
2224             icon = getFallbackIcon();
2225             info.usingFallbackIcon = true;
2226         }
2227         info.setIcon(icon);
2228 
2229         // from the resource
2230         ComponentName key = lai.getComponentName();
2231         if (labelCache != null && labelCache.containsKey(key)) {
2232             info.title = labelCache.get(key);
2233         } else {
2234             info.title = lai.getLabel();
2235             if (labelCache != null) {
2236                 labelCache.put(key, info.title);
2237             }
2238         }
2239         // from the db
2240         if (info.title == null) {
2241             if (c != null) {
2242                 info.title =  c.getString(titleIndex);
2243             }
2244         }
2245         // fall back to the class name of the activity
2246         if (info.title == null) {
2247             info.title = componentName.getClassName();
2248         }
2249 
2250         info.contentDescription = mApp.getPackageManager().getUserBadgedLabel(info.title, user);
2251         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
2252         return info;
2253     }
2254 
2255     /**
2256      * Returns the set of workspace ShortcutInfos with the specified intent.
2257      */
getWorkspaceShortcutItemInfosWithIntent(Intent intent)2258     static ArrayList<ItemInfo> getWorkspaceShortcutItemInfosWithIntent(Intent intent) {
2259         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
2260         synchronized (sBgLock) {
2261             for (ItemInfo info : sBgWorkspaceItems) {
2262                 if (info instanceof ShortcutInfo) {
2263                     ShortcutInfo shortcut = (ShortcutInfo) info;
2264                     if (shortcut.intent.toUri(0).equals(intent.toUri(0))) {
2265                         items.add(shortcut);
2266                     }
2267                 }
2268             }
2269         }
2270         return items;
2271     }
2272 
2273     /**
2274      * Make an ShortcutInfo object for a shortcut that isn't an application.
2275      */
getShortcutInfo(Cursor c, Context context, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, int titleIndex)2276     private ShortcutInfo getShortcutInfo(Cursor c, Context context,
2277             int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
2278             int titleIndex) {
2279 
2280         Bitmap icon = null;
2281         final ShortcutInfo info = new ShortcutInfo();
2282         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
2283 
2284         // TODO: If there's an explicit component and we can't install that, delete it.
2285 
2286         info.title = c.getString(titleIndex);
2287         info.contentDescription = mApp.getPackageManager().getUserBadgedLabel(
2288                 info.title, info.user);
2289 
2290         int iconType = c.getInt(iconTypeIndex);
2291         switch (iconType) {
2292         case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
2293             String packageName = c.getString(iconPackageIndex);
2294             String resourceName = c.getString(iconResourceIndex);
2295             PackageManager packageManager = context.getPackageManager();
2296             info.customIcon = false;
2297                 // the resource
2298                 try {
2299                     Resources resources = packageManager.getResourcesForApplication(packageName);
2300                     if (resources != null) {
2301                         final int id = resources.getIdentifier(resourceName, null, null);
2302                         icon = Utilities.createIconBitmap(
2303                                 mIconCache.getFullResIcon(resources, id, Process.myUserHandle()),
2304                                 context);
2305                     }
2306                 } catch (Exception e) {
2307                     // drop this. we have other places to look for icons
2308                 }
2309             // the db
2310             if (icon == null) {
2311                 icon = getIconFromCursor(c, iconIndex, context);
2312             }
2313             // the fallback icon
2314             if (icon == null) {
2315                 icon = getFallbackIcon();
2316                 info.usingFallbackIcon = true;
2317             }
2318             break;
2319         case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
2320             icon = getIconFromCursor(c, iconIndex, context);
2321             if (icon == null) {
2322                 icon = getFallbackIcon();
2323                 info.customIcon = false;
2324                 info.usingFallbackIcon = true;
2325             } else {
2326                 info.customIcon = true;
2327             }
2328             break;
2329         default:
2330             icon = getFallbackIcon();
2331             info.usingFallbackIcon = true;
2332             info.customIcon = false;
2333             break;
2334         }
2335         info.setIcon(icon);
2336         return info;
2337     }
2338 
getIconFromCursor(Cursor c, int iconIndex, Context context)2339     Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
2340         @SuppressWarnings("all") // suppress dead code warning
2341         final boolean debug = false;
2342         if (debug) {
2343             Log.d(TAG, "getIconFromCursor app="
2344                     + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
2345         }
2346         byte[] data = c.getBlob(iconIndex);
2347         try {
2348             return Utilities.createIconBitmap(
2349                     BitmapFactory.decodeByteArray(data, 0, data.length), context);
2350         } catch (Exception e) {
2351             return null;
2352         }
2353     }
2354 
addShortcut(Context context, Intent data, long container, int screen, int cellX, int cellY, boolean notify)2355     ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
2356             int cellX, int cellY, boolean notify) {
2357         final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
2358         if (info == null) {
2359             return null;
2360         }
2361         addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
2362 
2363         return info;
2364     }
2365 
2366     /**
2367      * Attempts to find an AppWidgetProviderInfo that matches the given component.
2368      */
findAppWidgetProviderInfoWithComponent(Context context, ComponentName component)2369     AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
2370             ComponentName component) {
2371         List<AppWidgetProviderInfo> widgets =
2372             AppWidgetManager.getInstance(context).getInstalledProviders();
2373         for (AppWidgetProviderInfo info : widgets) {
2374             if (info.provider.equals(component)) {
2375                 return info;
2376             }
2377         }
2378         return null;
2379     }
2380 
2381     /**
2382      * Returns a list of all the widgets that can handle configuration with a particular mimeType.
2383      */
resolveWidgetsForMimeType(Context context, String mimeType)2384     List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
2385         final PackageManager packageManager = context.getPackageManager();
2386         final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
2387             new ArrayList<WidgetMimeTypeHandlerData>();
2388 
2389         final Intent supportsIntent =
2390             new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
2391         supportsIntent.setType(mimeType);
2392 
2393         // Create a set of widget configuration components that we can test against
2394         final List<AppWidgetProviderInfo> widgets =
2395             AppWidgetManager.getInstance(context).getInstalledProviders();
2396         final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
2397             new HashMap<ComponentName, AppWidgetProviderInfo>();
2398         for (AppWidgetProviderInfo info : widgets) {
2399             configurationComponentToWidget.put(info.configure, info);
2400         }
2401 
2402         // Run through each of the intents that can handle this type of clip data, and cross
2403         // reference them with the components that are actual configuration components
2404         final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
2405                 PackageManager.MATCH_DEFAULT_ONLY);
2406         for (ResolveInfo info : activities) {
2407             final ActivityInfo activityInfo = info.activityInfo;
2408             final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
2409                     activityInfo.name);
2410             if (configurationComponentToWidget.containsKey(infoComponent)) {
2411                 supportedConfigurationActivities.add(
2412                         new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
2413                                 configurationComponentToWidget.get(infoComponent)));
2414             }
2415         }
2416         return supportedConfigurationActivities;
2417     }
2418 
infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon)2419     ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
2420         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
2421         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
2422         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
2423         UserHandle user = data.getParcelableExtra(ItemInfo.EXTRA_PROFILE);
2424         if (user == null) user = Process.myUserHandle();
2425 
2426         if (intent == null) {
2427             // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
2428             Log.e(TAG, "Can't construct ShorcutInfo with null intent");
2429             return null;
2430         }
2431 
2432         Bitmap icon = null;
2433         boolean customIcon = false;
2434         ShortcutIconResource iconResource = null;
2435 
2436         if (bitmap != null && bitmap instanceof Bitmap) {
2437             icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
2438             customIcon = true;
2439         } else {
2440             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
2441             if (extra != null && extra instanceof ShortcutIconResource) {
2442                 try {
2443                     iconResource = (ShortcutIconResource) extra;
2444                     final PackageManager packageManager = context.getPackageManager();
2445                     Resources resources = packageManager.getResourcesForApplication(
2446                             iconResource.packageName);
2447                     final int id = resources.getIdentifier(iconResource.resourceName, null, null);
2448                     icon = Utilities.createIconBitmap(
2449                             mIconCache.getFullResIcon(resources, id, user),
2450                             context);
2451                 } catch (Exception e) {
2452                     Log.w(TAG, "Could not load shortcut icon: " + extra);
2453                 }
2454             }
2455         }
2456 
2457         final ShortcutInfo info = new ShortcutInfo();
2458 
2459         if (icon == null) {
2460             if (fallbackIcon != null) {
2461                 icon = fallbackIcon;
2462             } else {
2463                 icon = getFallbackIcon();
2464                 info.usingFallbackIcon = true;
2465             }
2466         }
2467         info.setIcon(icon);
2468 
2469         info.title = name;
2470         info.contentDescription = mApp.getPackageManager().getUserBadgedLabel(name, info.user);
2471         info.intent = intent;
2472         info.customIcon = customIcon;
2473         info.iconResource = iconResource;
2474 
2475         return info;
2476     }
2477 
queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c, int iconIndex)2478     boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
2479             int iconIndex) {
2480         // If apps can't be on SD, don't even bother.
2481         if (!mAppsCanBeOnRemoveableStorage) {
2482             return false;
2483         }
2484         // If this icon doesn't have a custom icon, check to see
2485         // what's stored in the DB, and if it doesn't match what
2486         // we're going to show, store what we are going to show back
2487         // into the DB.  We do this so when we're loading, if the
2488         // package manager can't find an icon (for example because
2489         // the app is on SD) then we can use that instead.
2490         if (!info.customIcon && !info.usingFallbackIcon) {
2491             cache.put(info, c.getBlob(iconIndex));
2492             return true;
2493         }
2494         return false;
2495     }
updateSavedIcon(Context context, ShortcutInfo info, byte[] data)2496     void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
2497         boolean needSave = false;
2498         try {
2499             if (data != null) {
2500                 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
2501                 Bitmap loaded = info.getIcon(mIconCache);
2502                 needSave = !saved.sameAs(loaded);
2503             } else {
2504                 needSave = true;
2505             }
2506         } catch (Exception e) {
2507             needSave = true;
2508         }
2509         if (needSave) {
2510             Log.d(TAG, "going to save icon bitmap for info=" + info);
2511             // This is slower than is ideal, but this only happens once
2512             // or when the app is updated with a new icon.
2513             updateItemInDatabase(context, info);
2514         }
2515     }
2516 
2517     /**
2518      * Return an existing FolderInfo object if we have encountered this ID previously,
2519      * or make a new one.
2520      */
findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id)2521     private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
2522         // See if a placeholder was created for us already
2523         FolderInfo folderInfo = folders.get(id);
2524         if (folderInfo == null) {
2525             // No placeholder -- create a new instance
2526             folderInfo = new FolderInfo();
2527             folders.put(id, folderInfo);
2528         }
2529         return folderInfo;
2530     }
2531 
getAppNameComparator()2532     public static final Comparator<ApplicationInfo> getAppNameComparator() {
2533         final Collator collator = Collator.getInstance();
2534         return new Comparator<ApplicationInfo>() {
2535             public final int compare(ApplicationInfo a, ApplicationInfo b) {
2536                 if (a.user.equals(b.user)) {
2537                     int result = collator.compare(a.title.toString(), b.title.toString());
2538                     if (result == 0) {
2539                         result = a.componentName.compareTo(b.componentName);
2540                     }
2541                     return result;
2542                 } else {
2543                     // TODO: Order this based on profile type rather than string compares.
2544                     return a.user.toString().compareTo(b.user.toString());
2545                 }
2546             }
2547         };
2548     }
2549     public static final Comparator<ApplicationInfo> APP_INSTALL_TIME_COMPARATOR
2550             = new Comparator<ApplicationInfo>() {
2551         public final int compare(ApplicationInfo a, ApplicationInfo b) {
2552             if (a.firstInstallTime < b.firstInstallTime) return 1;
2553             if (a.firstInstallTime > b.firstInstallTime) return -1;
2554             return 0;
2555         }
2556     };
2557     public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
2558         final Collator collator = Collator.getInstance();
2559         return new Comparator<AppWidgetProviderInfo>() {
2560             public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
2561                 return collator.compare(a.label.toString(), b.label.toString());
2562             }
2563         };
2564     }
2565     static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
2566         if (info.activityInfo != null) {
2567             return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
2568         } else {
2569             return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
2570         }
2571     }
2572     public static class ShortcutNameComparator implements Comparator<LauncherActivityInfo> {
2573         private Collator mCollator;
2574         private HashMap<Object, CharSequence> mLabelCache;
2575 
2576         ShortcutNameComparator() {
2577             mLabelCache = new HashMap<Object, CharSequence>();
2578             mCollator = Collator.getInstance();
2579         }
2580 
2581         ShortcutNameComparator(HashMap<Object, CharSequence> labelCache) {
2582             mLabelCache = labelCache;
2583             mCollator = Collator.getInstance();
2584         }
2585 
2586         @Override
2587         public final int compare(LauncherActivityInfo a, LauncherActivityInfo b) {
2588             CharSequence labelA, labelB;
2589             ComponentName keyA = a.getComponentName();
2590             ComponentName keyB = b.getComponentName();
2591             if (mLabelCache.containsKey(keyA)) {
2592                 labelA = mLabelCache.get(keyA);
2593             } else {
2594                 labelA = a.getLabel().toString();
2595 
2596                 mLabelCache.put(keyA, labelA);
2597             }
2598             if (mLabelCache.containsKey(keyB)) {
2599                 labelB = mLabelCache.get(keyB);
2600             } else {
2601                 labelB = b.getLabel().toString();
2602 
2603                 mLabelCache.put(keyB, labelB);
2604             }
2605             return mCollator.compare(labelA, labelB);
2606         }
2607     };
2608 
2609     public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
2610         private Collator mCollator;
2611         private PackageManager mPackageManager;
2612         private HashMap<Object, String> mLabelCache;
2613 
2614         WidgetAndShortcutNameComparator(PackageManager pm) {
2615             mPackageManager = pm;
2616             mLabelCache = new HashMap<Object, String>();
2617             mCollator = Collator.getInstance();
2618         }
2619         public final int compare(Object a, Object b) {
2620             String labelA, labelB;
2621             if (mLabelCache.containsKey(a)) {
2622                 labelA = mLabelCache.get(a);
2623             } else {
2624                 labelA = (a instanceof AppWidgetProviderInfo) ?
2625                     ((AppWidgetProviderInfo) a).loadLabel(mPackageManager) :
2626                     ((ResolveInfo) a).loadLabel(mPackageManager).toString();
2627                 mLabelCache.put(a, labelA);
2628             }
2629             if (mLabelCache.containsKey(b)) {
2630                 labelB = mLabelCache.get(b);
2631             } else {
2632                 labelB = (b instanceof AppWidgetProviderInfo) ?
2633                     ((AppWidgetProviderInfo) b).loadLabel(mPackageManager) :
2634                     ((ResolveInfo) b).loadLabel(mPackageManager).toString();
2635                 mLabelCache.put(b, labelB);
2636             }
2637             final int compareResult = mCollator.compare(labelA, labelB);
2638             if (compareResult != 0) {
2639                 return compareResult;
2640             }
2641             return 0;
2642         }
2643     };
2644 
2645     public void dumpState() {
2646         Log.d(TAG, "mCallbacks=" + mCallbacks);
2647         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
2648         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
2649         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
2650         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
2651         if (mLoaderTask != null) {
2652             mLoaderTask.dumpState();
2653         } else {
2654             Log.d(TAG, "mLoaderTask=null");
2655         }
2656     }
2657 }
2658