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