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