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