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