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