LauncherModel.java revision 845ba3b17b83a2b11d79c6fb076cf96ab4a737df
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher2; 18 19import java.lang.ref.WeakReference; 20import java.net.URISyntaxException; 21import java.text.Collator; 22import java.util.ArrayList; 23import java.util.Collections; 24import java.util.Comparator; 25import java.util.HashMap; 26import java.util.List; 27 28import android.appwidget.AppWidgetManager; 29import android.appwidget.AppWidgetProviderInfo; 30import android.content.BroadcastReceiver; 31import android.content.ComponentName; 32import android.content.ContentProviderClient; 33import android.content.ContentResolver; 34import android.content.ContentValues; 35import android.content.Context; 36import android.content.Intent; 37import android.content.Intent.ShortcutIconResource; 38import android.content.pm.ActivityInfo; 39import android.content.pm.PackageManager; 40import android.content.pm.ProviderInfo; 41import android.content.pm.ResolveInfo; 42import android.content.res.Resources; 43import android.database.Cursor; 44import android.graphics.Bitmap; 45import android.graphics.BitmapFactory; 46import android.net.Uri; 47import android.os.Handler; 48import android.os.HandlerThread; 49import android.os.Parcelable; 50import android.os.Process; 51import android.os.RemoteException; 52import android.os.SystemClock; 53import android.util.Log; 54 55import com.android.launcher.R; 56 57/** 58 * Maintains in-memory state of the Launcher. It is expected that there should be only one 59 * LauncherModel object held in a static. Also provide APIs for updating the database state 60 * for the Launcher. 61 */ 62public class LauncherModel extends BroadcastReceiver { 63 static final boolean DEBUG_LOADERS = false; 64 static final String TAG = "Launcher.Model"; 65 66 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 67 private int mBatchSize; // 0 is all apps at once 68 private int mAllAppsLoadDelay; // milliseconds between batches 69 70 private final LauncherApplication mApp; 71 private final Object mLock = new Object(); 72 private DeferredHandler mHandler = new DeferredHandler(); 73 private HandlerThread mWorkerThread; 74 private Handler mWorker; 75 private LoaderTask mLoaderTask; 76 77 // We start off with everything not loaded. After that, we assume that 78 // our monitoring of the package manager provides all updates and we never 79 // need to do a requery. These are only ever touched from the loader thread. 80 private boolean mWorkspaceLoaded; 81 private boolean mAllAppsLoaded; 82 83 private WeakReference<Callbacks> mCallbacks; 84 85 private AllAppsList mAllAppsList; // only access in worker thread 86 private IconCache mIconCache; 87 final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); 88 final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); 89 final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>(); 90 91 private Bitmap mDefaultIcon; 92 93 private static int mCellCountX; 94 private static int mCellCountY; 95 96 public interface Callbacks { 97 public int getCurrentWorkspaceScreen(); 98 public void startBinding(); 99 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); 100 public void bindFolders(HashMap<Long,FolderInfo> folders); 101 public void finishBindingItems(); 102 public void bindAppWidget(LauncherAppWidgetInfo info); 103 public void bindAllApplications(ArrayList<ApplicationInfo> apps); 104 public void bindAppsAdded(ArrayList<ApplicationInfo> apps); 105 public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); 106 public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent); 107 public void bindPackagesUpdated(); 108 public boolean isAllAppsVisible(); 109 } 110 111 LauncherModel(LauncherApplication app, IconCache iconCache) { 112 mApp = app; 113 mAllAppsList = new AllAppsList(iconCache); 114 mIconCache = iconCache; 115 116 mDefaultIcon = Utilities.createIconBitmap( 117 app.getPackageManager().getDefaultActivityIcon(), app); 118 119 mAllAppsLoadDelay = app.getResources().getInteger(R.integer.config_allAppsBatchLoadDelay); 120 121 mBatchSize = app.getResources().getInteger(R.integer.config_allAppsBatchSize); 122 123 mWorkerThread = new HandlerThread("launcher-loader"); 124 mWorkerThread.start(); 125 mWorker = new Handler(mWorkerThread.getLooper()); 126 } 127 128 public Bitmap getFallbackIcon() { 129 return Bitmap.createBitmap(mDefaultIcon); 130 } 131 132 /** 133 * Adds an item to the DB if it was not created previously, or move it to a new 134 * <container, screen, cellX, cellY> 135 */ 136 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 137 int screen, int cellX, int cellY) { 138 if (item.container == ItemInfo.NO_ID) { 139 // From all apps 140 addItemToDatabase(context, item, container, screen, cellX, cellY, false); 141 } else { 142 // From somewhere else 143 moveItemInDatabase(context, item, container, screen, cellX, cellY); 144 } 145 } 146 147 /** 148 * Move an item in the DB to a new <container, screen, cellX, cellY> 149 */ 150 static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, 151 int cellX, int cellY) { 152 153 item.container = container; 154 item.screen = screen; 155 item.cellX = cellX; 156 item.cellY = cellY; 157 158 final ContentValues values = new ContentValues(); 159 final ContentResolver cr = context.getContentResolver(); 160 161 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 162 values.put(LauncherSettings.Favorites.CELLX, cellX); 163 values.put(LauncherSettings.Favorites.CELLY, cellY); 164 values.put(LauncherSettings.Favorites.SCREEN, item.screen); 165 166 cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); 167 } 168 169 /** 170 * Returns true if the shortcuts already exists in the database. 171 * we identify a shortcut by its title and intent. 172 */ 173 static boolean shortcutExists(Context context, String title, Intent intent) { 174 final ContentResolver cr = context.getContentResolver(); 175 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, 176 new String[] { "title", "intent" }, "title=? and intent=?", 177 new String[] { title, intent.toUri(0) }, null); 178 boolean result = false; 179 try { 180 result = c.moveToFirst(); 181 } finally { 182 c.close(); 183 } 184 return result; 185 } 186 187 /** 188 * Returns an ItemInfo array containing all the items in the LauncherModel. 189 * The ItemInfo.id is not set through this function. 190 */ 191 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) { 192 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); 193 final ContentResolver cr = context.getContentResolver(); 194 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] { 195 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER, 196 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, 197 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null); 198 199 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 200 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 201 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 202 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 203 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 204 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); 205 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); 206 207 try { 208 while (c.moveToNext()) { 209 ItemInfo item = new ItemInfo(); 210 item.cellX = c.getInt(cellXIndex); 211 item.cellY = c.getInt(cellYIndex); 212 item.spanX = c.getInt(spanXIndex); 213 item.spanY = c.getInt(spanYIndex); 214 item.container = c.getInt(containerIndex); 215 item.itemType = c.getInt(itemTypeIndex); 216 item.screen = c.getInt(screenIndex); 217 218 items.add(item); 219 } 220 } catch (Exception e) { 221 items.clear(); 222 } finally { 223 c.close(); 224 } 225 226 return items; 227 } 228 229 /** 230 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. 231 */ 232 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { 233 final ContentResolver cr = context.getContentResolver(); 234 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, 235 "_id=? and (itemType=? or itemType=?)", 236 new String[] { String.valueOf(id), 237 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER), 238 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null); 239 240 try { 241 if (c.moveToFirst()) { 242 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 243 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 244 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 245 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 246 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 247 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 248 249 FolderInfo folderInfo = null; 250 switch (c.getInt(itemTypeIndex)) { 251 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 252 folderInfo = findOrMakeUserFolder(folderList, id); 253 break; 254 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 255 folderInfo = findOrMakeLiveFolder(folderList, id); 256 break; 257 } 258 259 folderInfo.title = c.getString(titleIndex); 260 folderInfo.id = id; 261 folderInfo.container = c.getInt(containerIndex); 262 folderInfo.screen = c.getInt(screenIndex); 263 folderInfo.cellX = c.getInt(cellXIndex); 264 folderInfo.cellY = c.getInt(cellYIndex); 265 266 return folderInfo; 267 } 268 } finally { 269 c.close(); 270 } 271 272 return null; 273 } 274 275 /** 276 * Add an item to the database in a specified container. Sets the container, screen, cellX and 277 * cellY fields of the item. Also assigns an ID to the item. 278 */ 279 static void addItemToDatabase(Context context, ItemInfo item, long container, 280 int screen, int cellX, int cellY, boolean notify) { 281 item.container = container; 282 item.screen = screen; 283 item.cellX = cellX; 284 item.cellY = cellY; 285 286 final ContentValues values = new ContentValues(); 287 final ContentResolver cr = context.getContentResolver(); 288 item.onAddToDatabase(values); 289 290 item.updateValuesWithCoordinates(values, cellX, cellY); 291 292 Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : 293 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); 294 295 if (result != null) { 296 item.id = Integer.parseInt(result.getPathSegments().get(1)); 297 } 298 } 299 300 /** 301 * Creates a new unique child id, for a given cell span across all layouts. 302 */ 303 static int getCellLayoutChildId( 304 int cellId, int screen, int localCellX, int localCellY, int spanX, int spanY) { 305 return ((cellId & 0xFF) << 24) 306 | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF); 307 } 308 309 static int getCellCountX() { 310 return mCellCountX; 311 } 312 313 static int getCellCountY() { 314 return mCellCountY; 315 } 316 317 /** 318 * Updates the model orientation helper to take into account the current layout dimensions 319 * when performing local/canonical coordinate transformations. 320 */ 321 static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) { 322 mCellCountX = shortAxisCellCount; 323 mCellCountY = longAxisCellCount; 324 } 325 326 /** 327 * Update an item to the database in a specified container. 328 */ 329 static void updateItemInDatabase(Context context, ItemInfo item) { 330 final ContentValues values = new ContentValues(); 331 final ContentResolver cr = context.getContentResolver(); 332 333 item.onAddToDatabase(values); 334 item.updateValuesWithCoordinates(values, item.cellX, item.cellY); 335 336 cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); 337 } 338 339 /** 340 * Removes the specified item from the database 341 * @param context 342 * @param item 343 */ 344 static void deleteItemFromDatabase(Context context, ItemInfo item) { 345 final ContentResolver cr = context.getContentResolver(); 346 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false); 347 new Thread("deleteItemFromDatabase") { 348 public void run() { 349 cr.delete(uriToDelete, null, null); 350 } 351 }.start(); 352 } 353 354 /** 355 * Remove the contents of the specified folder from the database 356 */ 357 static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) { 358 final ContentResolver cr = context.getContentResolver(); 359 360 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); 361 cr.delete(LauncherSettings.Favorites.CONTENT_URI, 362 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 363 } 364 365 /** 366 * Set this as the current Launcher activity object for the loader. 367 */ 368 public void initialize(Callbacks callbacks) { 369 synchronized (mLock) { 370 mCallbacks = new WeakReference<Callbacks>(callbacks); 371 } 372 } 373 374 /** 375 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 376 * ACTION_PACKAGE_CHANGED. 377 */ 378 public void onReceive(Context context, Intent intent) { 379 if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent); 380 381 final String action = intent.getAction(); 382 383 if (Intent.ACTION_PACKAGE_CHANGED.equals(action) 384 || Intent.ACTION_PACKAGE_REMOVED.equals(action) 385 || Intent.ACTION_PACKAGE_ADDED.equals(action)) { 386 final String packageName = intent.getData().getSchemeSpecificPart(); 387 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 388 389 int op = PackageUpdatedTask.OP_NONE; 390 391 if (packageName == null || packageName.length() == 0) { 392 // they sent us a bad intent 393 return; 394 } 395 396 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 397 op = PackageUpdatedTask.OP_UPDATE; 398 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 399 if (!replacing) { 400 op = PackageUpdatedTask.OP_REMOVE; 401 } 402 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 403 // later, we will update the package at this time 404 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 405 if (!replacing) { 406 op = PackageUpdatedTask.OP_ADD; 407 } else { 408 op = PackageUpdatedTask.OP_UPDATE; 409 } 410 } 411 412 if (op != PackageUpdatedTask.OP_NONE) { 413 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); 414 } 415 416 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { 417 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 418 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); 419 420 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 421 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 422 enqueuePackageUpdated(new PackageUpdatedTask( 423 PackageUpdatedTask.OP_UNAVAILABLE, packages)); 424 } 425 } 426 427 public void startLoader(Context context, boolean isLaunching) { 428 synchronized (mLock) { 429 if (DEBUG_LOADERS) { 430 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 431 } 432 433 // Don't bother to start the thread if we know it's not going to do anything 434 if (mCallbacks != null && mCallbacks.get() != null) { 435 // If there is already one running, tell it to stop. 436 LoaderTask oldTask = mLoaderTask; 437 if (oldTask != null) { 438 if (oldTask.isLaunching()) { 439 // don't downgrade isLaunching if we're already running 440 isLaunching = true; 441 } 442 oldTask.stopLocked(); 443 } 444 mLoaderTask = new LoaderTask(context, isLaunching); 445 mWorker.post(mLoaderTask); 446 } 447 } 448 } 449 450 public void stopLoader() { 451 synchronized (mLock) { 452 if (mLoaderTask != null) { 453 mLoaderTask.stopLocked(); 454 } 455 } 456 } 457 458 /** 459 * Runnable for the thread that loads the contents of the launcher: 460 * - workspace icons 461 * - widgets 462 * - all apps icons 463 */ 464 private class LoaderTask implements Runnable { 465 private Context mContext; 466 private Thread mWaitThread; 467 private boolean mIsLaunching; 468 private boolean mStopped; 469 private boolean mLoadAndBindStepFinished; 470 471 LoaderTask(Context context, boolean isLaunching) { 472 mContext = context; 473 mIsLaunching = isLaunching; 474 } 475 476 boolean isLaunching() { 477 return mIsLaunching; 478 } 479 480 private void loadAndBindWorkspace() { 481 // Load the workspace 482 483 // For now, just always reload the workspace. It's ~100 ms vs. the 484 // binding which takes many hundreds of ms. 485 // We can reconsider. 486 if (DEBUG_LOADERS) { 487 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 488 } 489 if (true || !mWorkspaceLoaded) { 490 loadWorkspace(); 491 if (mStopped) { 492 return; 493 } 494 mWorkspaceLoaded = true; 495 } 496 497 // Bind the workspace 498 bindWorkspace(); 499 } 500 501 private void waitForIdle() { 502 // Wait until the either we're stopped or the other threads are done. 503 // This way we don't start loading all apps until the workspace has settled 504 // down. 505 synchronized (LoaderTask.this) { 506 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 507 508 mHandler.postIdle(new Runnable() { 509 public void run() { 510 synchronized (LoaderTask.this) { 511 mLoadAndBindStepFinished = true; 512 if (DEBUG_LOADERS) { 513 Log.d(TAG, "done with previous binding step"); 514 } 515 LoaderTask.this.notify(); 516 } 517 } 518 }); 519 520 while (!mStopped && !mLoadAndBindStepFinished) { 521 try { 522 this.wait(); 523 } catch (InterruptedException ex) { 524 // Ignore 525 } 526 } 527 if (DEBUG_LOADERS) { 528 Log.d(TAG, "waited " 529 + (SystemClock.uptimeMillis()-workspaceWaitTime) 530 + "ms for previous step to finish binding"); 531 } 532 } 533 } 534 535 public void run() { 536 // Optimize for end-user experience: if the Launcher is up and // running with the 537 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 538 // workspace first (default). 539 final Callbacks cbk = mCallbacks.get(); 540 final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true; 541 542 keep_running: { 543 // Elevate priority when Home launches for the first time to avoid 544 // starving at boot time. Staring at a blank home is not cool. 545 synchronized (mLock) { 546 android.os.Process.setThreadPriority(mIsLaunching 547 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 548 } 549 if (loadWorkspaceFirst) { 550 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 551 loadAndBindWorkspace(); 552 } else { 553 if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); 554 loadAndBindAllApps(); 555 } 556 557 if (mStopped) { 558 break keep_running; 559 } 560 561 // Whew! Hard work done. Slow us down, and wait until the UI thread has 562 // settled down. 563 synchronized (mLock) { 564 if (mIsLaunching) { 565 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 566 } 567 } 568 waitForIdle(); 569 570 // second step 571 if (loadWorkspaceFirst) { 572 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 573 loadAndBindAllApps(); 574 } else { 575 if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); 576 loadAndBindWorkspace(); 577 } 578 } 579 580 // Clear out this reference, otherwise we end up holding it until all of the 581 // callback runnables are done. 582 mContext = null; 583 584 synchronized (mLock) { 585 // If we are still the last one to be scheduled, remove ourselves. 586 if (mLoaderTask == this) { 587 mLoaderTask = null; 588 } 589 } 590 591 // Trigger a gc to try to clean up after the stuff is done, since the 592 // renderscript allocations aren't charged to the java heap. 593 if (mStopped) { 594 mHandler.post(new Runnable() { 595 public void run() { 596 System.gc(); 597 } 598 }); 599 } else { 600 mHandler.postIdle(new Runnable() { 601 public void run() { 602 System.gc(); 603 } 604 }); 605 } 606 } 607 608 public void stopLocked() { 609 synchronized (LoaderTask.this) { 610 mStopped = true; 611 this.notify(); 612 } 613 } 614 615 /** 616 * Gets the callbacks object. If we've been stopped, or if the launcher object 617 * has somehow been garbage collected, return null instead. Pass in the Callbacks 618 * object that was around when the deferred message was scheduled, and if there's 619 * a new Callbacks object around then also return null. This will save us from 620 * calling onto it with data that will be ignored. 621 */ 622 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 623 synchronized (mLock) { 624 if (mStopped) { 625 return null; 626 } 627 628 if (mCallbacks == null) { 629 return null; 630 } 631 632 final Callbacks callbacks = mCallbacks.get(); 633 if (callbacks != oldCallbacks) { 634 return null; 635 } 636 if (callbacks == null) { 637 Log.w(TAG, "no mCallbacks"); 638 return null; 639 } 640 641 return callbacks; 642 } 643 } 644 645 // check & update map of what's occupied; used to discard overlapping/invalid items 646 private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) { 647 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 648 return true; 649 } 650 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 651 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 652 if (occupied[item.screen][x][y] != null) { 653 Log.e(TAG, "Error loading shortcut " + item 654 + " into cell (" + item.screen + ":" 655 + x + "," + y 656 + ") occupied by " 657 + occupied[item.screen][x][y]); 658 return false; 659 } 660 } 661 } 662 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 663 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 664 occupied[item.screen][x][y] = item; 665 } 666 } 667 return true; 668 } 669 670 private void loadWorkspace() { 671 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 672 673 final Context context = mContext; 674 final ContentResolver contentResolver = context.getContentResolver(); 675 final PackageManager manager = context.getPackageManager(); 676 final AppWidgetManager widgets = AppWidgetManager.getInstance(context); 677 final boolean isSafeMode = manager.isSafeMode(); 678 679 mItems.clear(); 680 mAppWidgets.clear(); 681 mFolders.clear(); 682 683 final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); 684 685 final Cursor c = contentResolver.query( 686 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); 687 688 final ItemInfo occupied[][][] = 689 new ItemInfo[Launcher.SCREEN_COUNT][mCellCountX][mCellCountY]; 690 691 try { 692 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 693 final int intentIndex = c.getColumnIndexOrThrow 694 (LauncherSettings.Favorites.INTENT); 695 final int titleIndex = c.getColumnIndexOrThrow 696 (LauncherSettings.Favorites.TITLE); 697 final int iconTypeIndex = c.getColumnIndexOrThrow( 698 LauncherSettings.Favorites.ICON_TYPE); 699 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 700 final int iconPackageIndex = c.getColumnIndexOrThrow( 701 LauncherSettings.Favorites.ICON_PACKAGE); 702 final int iconResourceIndex = c.getColumnIndexOrThrow( 703 LauncherSettings.Favorites.ICON_RESOURCE); 704 final int containerIndex = c.getColumnIndexOrThrow( 705 LauncherSettings.Favorites.CONTAINER); 706 final int itemTypeIndex = c.getColumnIndexOrThrow( 707 LauncherSettings.Favorites.ITEM_TYPE); 708 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 709 LauncherSettings.Favorites.APPWIDGET_ID); 710 final int screenIndex = c.getColumnIndexOrThrow( 711 LauncherSettings.Favorites.SCREEN); 712 final int cellXIndex = c.getColumnIndexOrThrow 713 (LauncherSettings.Favorites.CELLX); 714 final int cellYIndex = c.getColumnIndexOrThrow 715 (LauncherSettings.Favorites.CELLY); 716 final int spanXIndex = c.getColumnIndexOrThrow 717 (LauncherSettings.Favorites.SPANX); 718 final int spanYIndex = c.getColumnIndexOrThrow( 719 LauncherSettings.Favorites.SPANY); 720 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 721 final int displayModeIndex = c.getColumnIndexOrThrow( 722 LauncherSettings.Favorites.DISPLAY_MODE); 723 724 ShortcutInfo info; 725 String intentDescription; 726 LauncherAppWidgetInfo appWidgetInfo; 727 int container; 728 long id; 729 Intent intent; 730 731 while (!mStopped && c.moveToNext()) { 732 try { 733 int itemType = c.getInt(itemTypeIndex); 734 735 switch (itemType) { 736 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 737 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 738 intentDescription = c.getString(intentIndex); 739 try { 740 intent = Intent.parseUri(intentDescription, 0); 741 } catch (URISyntaxException e) { 742 continue; 743 } 744 745 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 746 info = getShortcutInfo(manager, intent, context, c, iconIndex, 747 titleIndex); 748 } else { 749 info = getShortcutInfo(c, context, iconTypeIndex, 750 iconPackageIndex, iconResourceIndex, iconIndex, 751 titleIndex); 752 } 753 754 if (info != null) { 755 updateSavedIcon(context, info, c, iconIndex); 756 757 info.intent = intent; 758 info.id = c.getLong(idIndex); 759 container = c.getInt(containerIndex); 760 info.container = container; 761 info.screen = c.getInt(screenIndex); 762 info.cellX = c.getInt(cellXIndex); 763 info.cellY = c.getInt(cellYIndex); 764 765 // check & update map of what's occupied 766 if (!checkItemPlacement(occupied, info)) { 767 break; 768 } 769 770 switch (container) { 771 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 772 mItems.add(info); 773 break; 774 default: 775 // Item is in a user folder 776 UserFolderInfo folderInfo = 777 findOrMakeUserFolder(mFolders, container); 778 folderInfo.add(info); 779 break; 780 } 781 } else { 782 // Failed to load the shortcut, probably because the 783 // activity manager couldn't resolve it (maybe the app 784 // was uninstalled), or the db row was somehow screwed up. 785 // Delete it. 786 id = c.getLong(idIndex); 787 Log.e(TAG, "Error loading shortcut " + id + ", removing it"); 788 contentResolver.delete(LauncherSettings.Favorites.getContentUri( 789 id, false), null, null); 790 } 791 break; 792 793 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 794 id = c.getLong(idIndex); 795 UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id); 796 797 folderInfo.title = c.getString(titleIndex); 798 folderInfo.id = id; 799 container = c.getInt(containerIndex); 800 folderInfo.container = container; 801 folderInfo.screen = c.getInt(screenIndex); 802 folderInfo.cellX = c.getInt(cellXIndex); 803 folderInfo.cellY = c.getInt(cellYIndex); 804 805 // check & update map of what's occupied 806 if (!checkItemPlacement(occupied, folderInfo)) { 807 break; 808 } 809 switch (container) { 810 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 811 mItems.add(folderInfo); 812 break; 813 } 814 815 mFolders.put(folderInfo.id, folderInfo); 816 break; 817 818 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 819 id = c.getLong(idIndex); 820 Uri uri = Uri.parse(c.getString(uriIndex)); 821 822 // Make sure the live folder exists 823 final ProviderInfo providerInfo = 824 context.getPackageManager().resolveContentProvider( 825 uri.getAuthority(), 0); 826 827 if (providerInfo == null && !isSafeMode) { 828 itemsToRemove.add(id); 829 } else { 830 LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id); 831 intentDescription = c.getString(intentIndex); 832 intent = null; 833 if (intentDescription != null) { 834 try { 835 intent = Intent.parseUri(intentDescription, 0); 836 } catch (URISyntaxException e) { 837 // Ignore, a live folder might not have a base intent 838 } 839 } 840 841 liveFolderInfo.title = c.getString(titleIndex); 842 liveFolderInfo.id = id; 843 liveFolderInfo.uri = uri; 844 container = c.getInt(containerIndex); 845 liveFolderInfo.container = container; 846 liveFolderInfo.screen = c.getInt(screenIndex); 847 liveFolderInfo.cellX = c.getInt(cellXIndex); 848 liveFolderInfo.cellY = c.getInt(cellYIndex); 849 liveFolderInfo.baseIntent = intent; 850 liveFolderInfo.displayMode = c.getInt(displayModeIndex); 851 852 // check & update map of what's occupied 853 if (!checkItemPlacement(occupied, liveFolderInfo)) { 854 break; 855 } 856 857 loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex, 858 iconResourceIndex, liveFolderInfo); 859 860 switch (container) { 861 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 862 mItems.add(liveFolderInfo); 863 break; 864 } 865 mFolders.put(liveFolderInfo.id, liveFolderInfo); 866 } 867 break; 868 869 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 870 // Read all Launcher-specific widget details 871 int appWidgetId = c.getInt(appWidgetIdIndex); 872 id = c.getLong(idIndex); 873 874 final AppWidgetProviderInfo provider = 875 widgets.getAppWidgetInfo(appWidgetId); 876 877 if (!isSafeMode && (provider == null || provider.provider == null || 878 provider.provider.getPackageName() == null)) { 879 Log.e(TAG, "Deleting widget that isn't installed anymore: id=" 880 + id + " appWidgetId=" + appWidgetId); 881 itemsToRemove.add(id); 882 } else { 883 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); 884 appWidgetInfo.id = id; 885 appWidgetInfo.screen = c.getInt(screenIndex); 886 appWidgetInfo.cellX = c.getInt(cellXIndex); 887 appWidgetInfo.cellY = c.getInt(cellYIndex); 888 appWidgetInfo.spanX = c.getInt(spanXIndex); 889 appWidgetInfo.spanY = c.getInt(spanYIndex); 890 891 container = c.getInt(containerIndex); 892 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 893 Log.e(TAG, "Widget found where container " 894 + "!= CONTAINER_DESKTOP -- ignoring!"); 895 continue; 896 } 897 appWidgetInfo.container = c.getInt(containerIndex); 898 899 // check & update map of what's occupied 900 if (!checkItemPlacement(occupied, appWidgetInfo)) { 901 break; 902 } 903 904 mAppWidgets.add(appWidgetInfo); 905 } 906 break; 907 } 908 } catch (Exception e) { 909 Log.w(TAG, "Desktop items loading interrupted:", e); 910 } 911 } 912 } finally { 913 c.close(); 914 } 915 916 if (itemsToRemove.size() > 0) { 917 ContentProviderClient client = contentResolver.acquireContentProviderClient( 918 LauncherSettings.Favorites.CONTENT_URI); 919 // Remove dead items 920 for (long id : itemsToRemove) { 921 if (DEBUG_LOADERS) { 922 Log.d(TAG, "Removed id = " + id); 923 } 924 // Don't notify content observers 925 try { 926 client.delete(LauncherSettings.Favorites.getContentUri(id, false), 927 null, null); 928 } catch (RemoteException e) { 929 Log.w(TAG, "Could not remove id = " + id); 930 } 931 } 932 } 933 934 if (DEBUG_LOADERS) { 935 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 936 Log.d(TAG, "workspace layout: "); 937 for (int y = 0; y < mCellCountY; y++) { 938 String line = ""; 939 for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { 940 if (s > 0) { 941 line += " | "; 942 } 943 for (int x = 0; x < mCellCountX; x++) { 944 line += ((occupied[s][x][y] != null) ? "#" : "."); 945 } 946 } 947 Log.d(TAG, "[ " + line + " ]"); 948 } 949 } 950 } 951 952 /** 953 * Read everything out of our database. 954 */ 955 private void bindWorkspace() { 956 final long t = SystemClock.uptimeMillis(); 957 958 // Don't use these two variables in any of the callback runnables. 959 // Otherwise we hold a reference to them. 960 final Callbacks oldCallbacks = mCallbacks.get(); 961 if (oldCallbacks == null) { 962 // This launcher has exited and nobody bothered to tell us. Just bail. 963 Log.w(TAG, "LoaderTask running with no launcher"); 964 return; 965 } 966 967 int N; 968 // Tell the workspace that we're about to start firing items at it 969 mHandler.post(new Runnable() { 970 public void run() { 971 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 972 if (callbacks != null) { 973 callbacks.startBinding(); 974 } 975 } 976 }); 977 // Add the items to the workspace. 978 N = mItems.size(); 979 for (int i=0; i<N; i+=ITEMS_CHUNK) { 980 final int start = i; 981 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 982 mHandler.post(new Runnable() { 983 public void run() { 984 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 985 if (callbacks != null) { 986 callbacks.bindItems(mItems, start, start+chunkSize); 987 } 988 } 989 }); 990 } 991 mHandler.post(new Runnable() { 992 public void run() { 993 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 994 if (callbacks != null) { 995 callbacks.bindFolders(mFolders); 996 } 997 } 998 }); 999 // Wait until the queue goes empty. 1000 mHandler.post(new Runnable() { 1001 public void run() { 1002 if (DEBUG_LOADERS) { 1003 Log.d(TAG, "Going to start binding widgets soon."); 1004 } 1005 } 1006 }); 1007 // Bind the widgets, one at a time. 1008 // WARNING: this is calling into the workspace from the background thread, 1009 // but since getCurrentScreen() just returns the int, we should be okay. This 1010 // is just a hint for the order, and if it's wrong, we'll be okay. 1011 // TODO: instead, we should have that push the current screen into here. 1012 final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); 1013 N = mAppWidgets.size(); 1014 // once for the current screen 1015 for (int i=0; i<N; i++) { 1016 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 1017 if (widget.screen == currentScreen) { 1018 mHandler.post(new Runnable() { 1019 public void run() { 1020 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1021 if (callbacks != null) { 1022 callbacks.bindAppWidget(widget); 1023 } 1024 } 1025 }); 1026 } 1027 } 1028 // once for the other screens 1029 for (int i=0; i<N; i++) { 1030 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 1031 if (widget.screen != currentScreen) { 1032 mHandler.post(new Runnable() { 1033 public void run() { 1034 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1035 if (callbacks != null) { 1036 callbacks.bindAppWidget(widget); 1037 } 1038 } 1039 }); 1040 } 1041 } 1042 // Tell the workspace that we're done. 1043 mHandler.post(new Runnable() { 1044 public void run() { 1045 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1046 if (callbacks != null) { 1047 callbacks.finishBindingItems(); 1048 } 1049 } 1050 }); 1051 // If we're profiling, this is the last thing in the queue. 1052 mHandler.post(new Runnable() { 1053 public void run() { 1054 if (DEBUG_LOADERS) { 1055 Log.d(TAG, "bound workspace in " 1056 + (SystemClock.uptimeMillis()-t) + "ms"); 1057 } 1058 } 1059 }); 1060 } 1061 1062 private void loadAndBindAllApps() { 1063 if (DEBUG_LOADERS) { 1064 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 1065 } 1066 if (!mAllAppsLoaded) { 1067 loadAllAppsByBatch(); 1068 if (mStopped) { 1069 return; 1070 } 1071 mAllAppsLoaded = true; 1072 } else { 1073 onlyBindAllApps(); 1074 } 1075 } 1076 1077 private void onlyBindAllApps() { 1078 final Callbacks oldCallbacks = mCallbacks.get(); 1079 if (oldCallbacks == null) { 1080 // This launcher has exited and nobody bothered to tell us. Just bail. 1081 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 1082 return; 1083 } 1084 1085 // shallow copy 1086 final ArrayList<ApplicationInfo> list 1087 = (ArrayList<ApplicationInfo>)mAllAppsList.data.clone(); 1088 mHandler.post(new Runnable() { 1089 public void run() { 1090 final long t = SystemClock.uptimeMillis(); 1091 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1092 if (callbacks != null) { 1093 callbacks.bindAllApplications(list); 1094 } 1095 if (DEBUG_LOADERS) { 1096 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 1097 + (SystemClock.uptimeMillis()-t) + "ms"); 1098 } 1099 } 1100 }); 1101 1102 } 1103 1104 private void loadAllAppsByBatch() { 1105 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1106 1107 // Don't use these two variables in any of the callback runnables. 1108 // Otherwise we hold a reference to them. 1109 final Callbacks oldCallbacks = mCallbacks.get(); 1110 if (oldCallbacks == null) { 1111 // This launcher has exited and nobody bothered to tell us. Just bail. 1112 Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)"); 1113 return; 1114 } 1115 1116 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 1117 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 1118 1119 final PackageManager packageManager = mContext.getPackageManager(); 1120 List<ResolveInfo> apps = null; 1121 1122 int N = Integer.MAX_VALUE; 1123 1124 int startIndex; 1125 int i=0; 1126 int batchSize = -1; 1127 while (i < N && !mStopped) { 1128 if (i == 0) { 1129 mAllAppsList.clear(); 1130 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1131 apps = packageManager.queryIntentActivities(mainIntent, 0); 1132 if (DEBUG_LOADERS) { 1133 Log.d(TAG, "queryIntentActivities took " 1134 + (SystemClock.uptimeMillis()-qiaTime) + "ms"); 1135 } 1136 if (apps == null) { 1137 return; 1138 } 1139 N = apps.size(); 1140 if (DEBUG_LOADERS) { 1141 Log.d(TAG, "queryIntentActivities got " + N + " apps"); 1142 } 1143 if (N == 0) { 1144 // There are no apps?!? 1145 return; 1146 } 1147 if (mBatchSize == 0) { 1148 batchSize = N; 1149 } else { 1150 batchSize = mBatchSize; 1151 } 1152 1153 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1154 Collections.sort(apps, 1155 new ResolveInfo.DisplayNameComparator(packageManager)); 1156 if (DEBUG_LOADERS) { 1157 Log.d(TAG, "sort took " 1158 + (SystemClock.uptimeMillis()-sortTime) + "ms"); 1159 } 1160 } 1161 1162 final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1163 1164 startIndex = i; 1165 for (int j=0; i<N && j<batchSize; j++) { 1166 // This builds the icon bitmaps. 1167 mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i), mIconCache)); 1168 i++; 1169 } 1170 1171 final boolean first = i <= batchSize; 1172 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1173 final ArrayList<ApplicationInfo> added = mAllAppsList.added; 1174 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1175 1176 mHandler.post(new Runnable() { 1177 public void run() { 1178 final long t = SystemClock.uptimeMillis(); 1179 if (callbacks != null) { 1180 if (first) { 1181 callbacks.bindAllApplications(added); 1182 } else { 1183 callbacks.bindAppsAdded(added); 1184 } 1185 if (DEBUG_LOADERS) { 1186 Log.d(TAG, "bound " + added.size() + " apps in " 1187 + (SystemClock.uptimeMillis() - t) + "ms"); 1188 } 1189 } else { 1190 Log.i(TAG, "not binding apps: no Launcher activity"); 1191 } 1192 } 1193 }); 1194 1195 if (DEBUG_LOADERS) { 1196 Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in " 1197 + (SystemClock.uptimeMillis()-t2) + "ms"); 1198 } 1199 1200 if (mAllAppsLoadDelay > 0 && i < N) { 1201 try { 1202 if (DEBUG_LOADERS) { 1203 Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); 1204 } 1205 Thread.sleep(mAllAppsLoadDelay); 1206 } catch (InterruptedException exc) { } 1207 } 1208 } 1209 1210 if (DEBUG_LOADERS) { 1211 Log.d(TAG, "cached all " + N + " apps in " 1212 + (SystemClock.uptimeMillis()-t) + "ms" 1213 + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); 1214 } 1215 } 1216 1217 public void dumpState() { 1218 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 1219 Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread); 1220 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); 1221 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 1222 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 1223 } 1224 } 1225 1226 void enqueuePackageUpdated(PackageUpdatedTask task) { 1227 mWorker.post(task); 1228 } 1229 1230 private class PackageUpdatedTask implements Runnable { 1231 int mOp; 1232 String[] mPackages; 1233 1234 public static final int OP_NONE = 0; 1235 public static final int OP_ADD = 1; 1236 public static final int OP_UPDATE = 2; 1237 public static final int OP_REMOVE = 3; // uninstlled 1238 public static final int OP_UNAVAILABLE = 4; // external media unmounted 1239 1240 1241 public PackageUpdatedTask(int op, String[] packages) { 1242 mOp = op; 1243 mPackages = packages; 1244 } 1245 1246 public void run() { 1247 final Context context = mApp; 1248 1249 final String[] packages = mPackages; 1250 final int N = packages.length; 1251 switch (mOp) { 1252 case OP_ADD: 1253 for (int i=0; i<N; i++) { 1254 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); 1255 mAllAppsList.addPackage(context, packages[i]); 1256 } 1257 break; 1258 case OP_UPDATE: 1259 for (int i=0; i<N; i++) { 1260 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); 1261 mAllAppsList.updatePackage(context, packages[i]); 1262 } 1263 break; 1264 case OP_REMOVE: 1265 case OP_UNAVAILABLE: 1266 for (int i=0; i<N; i++) { 1267 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 1268 mAllAppsList.removePackage(packages[i]); 1269 } 1270 break; 1271 } 1272 1273 ArrayList<ApplicationInfo> added = null; 1274 ArrayList<ApplicationInfo> removed = null; 1275 ArrayList<ApplicationInfo> modified = null; 1276 1277 if (mAllAppsList.added.size() > 0) { 1278 added = mAllAppsList.added; 1279 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1280 } 1281 if (mAllAppsList.removed.size() > 0) { 1282 removed = mAllAppsList.removed; 1283 mAllAppsList.removed = new ArrayList<ApplicationInfo>(); 1284 for (ApplicationInfo info: removed) { 1285 mIconCache.remove(info.intent.getComponent()); 1286 } 1287 } 1288 if (mAllAppsList.modified.size() > 0) { 1289 modified = mAllAppsList.modified; 1290 mAllAppsList.modified = new ArrayList<ApplicationInfo>(); 1291 } 1292 1293 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 1294 if (callbacks == null) { 1295 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); 1296 return; 1297 } 1298 1299 if (added != null) { 1300 final ArrayList<ApplicationInfo> addedFinal = added; 1301 mHandler.post(new Runnable() { 1302 public void run() { 1303 if (callbacks == mCallbacks.get()) { 1304 callbacks.bindAppsAdded(addedFinal); 1305 } 1306 } 1307 }); 1308 } 1309 if (modified != null) { 1310 final ArrayList<ApplicationInfo> modifiedFinal = modified; 1311 mHandler.post(new Runnable() { 1312 public void run() { 1313 if (callbacks == mCallbacks.get()) { 1314 callbacks.bindAppsUpdated(modifiedFinal); 1315 } 1316 } 1317 }); 1318 } 1319 if (removed != null) { 1320 final boolean permanent = mOp != OP_UNAVAILABLE; 1321 final ArrayList<ApplicationInfo> removedFinal = removed; 1322 mHandler.post(new Runnable() { 1323 public void run() { 1324 if (callbacks == mCallbacks.get()) { 1325 callbacks.bindAppsRemoved(removedFinal, permanent); 1326 } 1327 } 1328 }); 1329 } 1330 1331 mHandler.post(new Runnable() { 1332 @Override 1333 public void run() { 1334 if (callbacks == mCallbacks.get()) { 1335 callbacks.bindPackagesUpdated(); 1336 } 1337 } 1338 }); 1339 } 1340 } 1341 1342 /** 1343 * This is called from the code that adds shortcuts from the intent receiver. This 1344 * doesn't have a Cursor, but 1345 */ 1346 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { 1347 return getShortcutInfo(manager, intent, context, null, -1, -1); 1348 } 1349 1350 /** 1351 * Make an ShortcutInfo object for a shortcut that is an application. 1352 * 1353 * If c is not null, then it will be used to fill in missing data like the title and icon. 1354 */ 1355 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, 1356 Cursor c, int iconIndex, int titleIndex) { 1357 Bitmap icon = null; 1358 final ShortcutInfo info = new ShortcutInfo(); 1359 1360 ComponentName componentName = intent.getComponent(); 1361 if (componentName == null) { 1362 return null; 1363 } 1364 1365 // TODO: See if the PackageManager knows about this case. If it doesn't 1366 // then return null & delete this. 1367 1368 // the resource -- This may implicitly give us back the fallback icon, 1369 // but don't worry about that. All we're doing with usingFallbackIcon is 1370 // to avoid saving lots of copies of that in the database, and most apps 1371 // have icons anyway. 1372 final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0); 1373 if (resolveInfo != null) { 1374 icon = mIconCache.getIcon(componentName, resolveInfo); 1375 } 1376 // the db 1377 if (icon == null) { 1378 if (c != null) { 1379 icon = getIconFromCursor(c, iconIndex); 1380 } 1381 } 1382 // the fallback icon 1383 if (icon == null) { 1384 icon = getFallbackIcon(); 1385 info.usingFallbackIcon = true; 1386 } 1387 info.setIcon(icon); 1388 1389 // from the resource 1390 if (resolveInfo != null) { 1391 info.title = resolveInfo.activityInfo.loadLabel(manager); 1392 } 1393 // from the db 1394 if (info.title == null) { 1395 if (c != null) { 1396 info.title = c.getString(titleIndex); 1397 } 1398 } 1399 // fall back to the class name of the activity 1400 if (info.title == null) { 1401 info.title = componentName.getClassName(); 1402 } 1403 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 1404 return info; 1405 } 1406 1407 /** 1408 * Make an ShortcutInfo object for a shortcut that isn't an application. 1409 */ 1410 private ShortcutInfo getShortcutInfo(Cursor c, Context context, 1411 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, 1412 int titleIndex) { 1413 1414 Bitmap icon = null; 1415 final ShortcutInfo info = new ShortcutInfo(); 1416 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 1417 1418 // TODO: If there's an explicit component and we can't install that, delete it. 1419 1420 info.title = c.getString(titleIndex); 1421 1422 int iconType = c.getInt(iconTypeIndex); 1423 switch (iconType) { 1424 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 1425 String packageName = c.getString(iconPackageIndex); 1426 String resourceName = c.getString(iconResourceIndex); 1427 PackageManager packageManager = context.getPackageManager(); 1428 info.customIcon = false; 1429 // the resource 1430 try { 1431 Resources resources = packageManager.getResourcesForApplication(packageName); 1432 if (resources != null) { 1433 final int id = resources.getIdentifier(resourceName, null, null); 1434 icon = Utilities.createIconBitmap(resources.getDrawable(id), context); 1435 } 1436 } catch (Exception e) { 1437 // drop this. we have other places to look for icons 1438 } 1439 // the db 1440 if (icon == null) { 1441 icon = getIconFromCursor(c, iconIndex); 1442 } 1443 // the fallback icon 1444 if (icon == null) { 1445 icon = getFallbackIcon(); 1446 info.usingFallbackIcon = true; 1447 } 1448 break; 1449 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 1450 icon = getIconFromCursor(c, iconIndex); 1451 if (icon == null) { 1452 icon = getFallbackIcon(); 1453 info.customIcon = false; 1454 info.usingFallbackIcon = true; 1455 } else { 1456 info.customIcon = true; 1457 } 1458 break; 1459 default: 1460 icon = getFallbackIcon(); 1461 info.usingFallbackIcon = true; 1462 info.customIcon = false; 1463 break; 1464 } 1465 info.setIcon(icon); 1466 return info; 1467 } 1468 1469 Bitmap getIconFromCursor(Cursor c, int iconIndex) { 1470 if (false) { 1471 Log.d(TAG, "getIconFromCursor app=" 1472 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); 1473 } 1474 byte[] data = c.getBlob(iconIndex); 1475 try { 1476 return BitmapFactory.decodeByteArray(data, 0, data.length); 1477 } catch (Exception e) { 1478 return null; 1479 } 1480 } 1481 1482 ShortcutInfo addShortcut(Context context, Intent data, 1483 int screen, int cellX, int cellY, boolean notify) { 1484 1485 final ShortcutInfo info = infoFromShortcutIntent(context, data); 1486 addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, 1487 screen, cellX, cellY, notify); 1488 1489 return info; 1490 } 1491 1492 private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) { 1493 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 1494 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 1495 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 1496 1497 Bitmap icon = null; 1498 boolean filtered = false; 1499 boolean customIcon = false; 1500 ShortcutIconResource iconResource = null; 1501 1502 if (bitmap != null && bitmap instanceof Bitmap) { 1503 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); 1504 filtered = true; 1505 customIcon = true; 1506 } else { 1507 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 1508 if (extra != null && extra instanceof ShortcutIconResource) { 1509 try { 1510 iconResource = (ShortcutIconResource) extra; 1511 final PackageManager packageManager = context.getPackageManager(); 1512 Resources resources = packageManager.getResourcesForApplication( 1513 iconResource.packageName); 1514 final int id = resources.getIdentifier(iconResource.resourceName, null, null); 1515 icon = Utilities.createIconBitmap(resources.getDrawable(id), context); 1516 } catch (Exception e) { 1517 Log.w(TAG, "Could not load shortcut icon: " + extra); 1518 } 1519 } 1520 } 1521 1522 final ShortcutInfo info = new ShortcutInfo(); 1523 1524 if (icon == null) { 1525 icon = getFallbackIcon(); 1526 info.usingFallbackIcon = true; 1527 } 1528 info.setIcon(icon); 1529 1530 info.title = name; 1531 info.intent = intent; 1532 info.customIcon = customIcon; 1533 info.iconResource = iconResource; 1534 1535 return info; 1536 } 1537 1538 private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, 1539 int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { 1540 1541 int iconType = c.getInt(iconTypeIndex); 1542 switch (iconType) { 1543 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 1544 String packageName = c.getString(iconPackageIndex); 1545 String resourceName = c.getString(iconResourceIndex); 1546 PackageManager packageManager = context.getPackageManager(); 1547 try { 1548 Resources resources = packageManager.getResourcesForApplication(packageName); 1549 final int id = resources.getIdentifier(resourceName, null, null); 1550 liveFolderInfo.icon = Utilities.createIconBitmap(resources.getDrawable(id), 1551 context); 1552 } catch (Exception e) { 1553 liveFolderInfo.icon = Utilities.createIconBitmap( 1554 context.getResources().getDrawable(R.drawable.ic_launcher_folder), 1555 context); 1556 } 1557 liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); 1558 liveFolderInfo.iconResource.packageName = packageName; 1559 liveFolderInfo.iconResource.resourceName = resourceName; 1560 break; 1561 default: 1562 liveFolderInfo.icon = Utilities.createIconBitmap( 1563 context.getResources().getDrawable(R.drawable.ic_launcher_folder), 1564 context); 1565 } 1566 } 1567 1568 void updateSavedIcon(Context context, ShortcutInfo info, Cursor c, int iconIndex) { 1569 // If this icon doesn't have a custom icon, check to see 1570 // what's stored in the DB, and if it doesn't match what 1571 // we're going to show, store what we are going to show back 1572 // into the DB. We do this so when we're loading, if the 1573 // package manager can't find an icon (for example because 1574 // the app is on SD) then we can use that instead. 1575 if (!info.customIcon && !info.usingFallbackIcon) { 1576 boolean needSave; 1577 byte[] data = c.getBlob(iconIndex); 1578 try { 1579 if (data != null) { 1580 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); 1581 Bitmap loaded = info.getIcon(mIconCache); 1582 needSave = !saved.sameAs(loaded); 1583 } else { 1584 needSave = true; 1585 } 1586 } catch (Exception e) { 1587 needSave = true; 1588 } 1589 if (needSave) { 1590 Log.d(TAG, "going to save icon bitmap for info=" + info); 1591 // This is slower than is ideal, but this only happens either 1592 // after the froyo OTA or when the app is updated with a new 1593 // icon. 1594 updateItemInDatabase(context, info); 1595 } 1596 } 1597 } 1598 1599 /** 1600 * Return an existing UserFolderInfo object if we have encountered this ID previously, 1601 * or make a new one. 1602 */ 1603 private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) { 1604 // See if a placeholder was created for us already 1605 FolderInfo folderInfo = folders.get(id); 1606 if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) { 1607 // No placeholder -- create a new instance 1608 folderInfo = new UserFolderInfo(); 1609 folders.put(id, folderInfo); 1610 } 1611 return (UserFolderInfo) folderInfo; 1612 } 1613 1614 /** 1615 * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a 1616 * new one. 1617 */ 1618 private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) { 1619 // See if a placeholder was created for us already 1620 FolderInfo folderInfo = folders.get(id); 1621 if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) { 1622 // No placeholder -- create a new instance 1623 folderInfo = new LiveFolderInfo(); 1624 folders.put(id, folderInfo); 1625 } 1626 return (LiveFolderInfo) folderInfo; 1627 } 1628 1629 private static String getLabel(PackageManager manager, ActivityInfo activityInfo) { 1630 String label = activityInfo.loadLabel(manager).toString(); 1631 if (label == null) { 1632 label = manager.getApplicationLabel(activityInfo.applicationInfo).toString(); 1633 if (label == null) { 1634 label = activityInfo.name; 1635 } 1636 } 1637 return label; 1638 } 1639 1640 private static final Collator sCollator = Collator.getInstance(); 1641 public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR 1642 = new Comparator<ApplicationInfo>() { 1643 public final int compare(ApplicationInfo a, ApplicationInfo b) { 1644 return sCollator.compare(a.title.toString(), b.title.toString()); 1645 } 1646 }; 1647 1648 public void dumpState() { 1649 Log.d(TAG, "mCallbacks=" + mCallbacks); 1650 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); 1651 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); 1652 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); 1653 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); 1654 Log.d(TAG, "mItems size=" + mItems.size()); 1655 if (mLoaderTask != null) { 1656 mLoaderTask.dumpState(); 1657 } else { 1658 Log.d(TAG, "mLoaderTask=null"); 1659 } 1660 } 1661} 1662