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