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