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