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