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