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