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