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