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