LauncherModel.java revision 34b02499432896b60cdb9f78c9d1019c80ef6ef5
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher2; 18 19import android.content.ComponentName; 20import android.content.ContentResolver; 21import android.content.ContentValues; 22import android.content.Intent; 23import android.content.Context; 24import android.content.pm.ActivityInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.ResolveInfo; 27import android.content.res.Resources; 28import android.database.Cursor; 29import android.graphics.Bitmap; 30import android.graphics.BitmapFactory; 31import android.graphics.drawable.Drawable; 32import android.net.Uri; 33import static android.util.Log.*; 34import android.util.Log; 35import android.os.Process; 36import android.os.SystemClock; 37 38import java.lang.ref.WeakReference; 39import java.net.URISyntaxException; 40import java.text.Collator; 41import java.util.ArrayList; 42import java.util.Comparator; 43import java.util.Collections; 44import java.util.HashMap; 45import java.util.List; 46 47/** 48 * Maintains in-memory state of the Launcher. It is expected that there should be only one 49 * LauncherModel object held in a static. Also provide APIs for updating the database state 50 * for the Launcher. 51 */ 52public class LauncherModel { 53 static final boolean DEBUG_LOADERS = true; 54 static final String TAG = "Launcher.Model"; 55 56 private final Object mLock = new Object(); 57 private DeferredHandler mHandler = new DeferredHandler(); 58 private Loader mLoader = new Loader(); 59 60 private WeakReference<Callbacks> mCallbacks; 61 62 private AllAppsList mAllAppsList = new AllAppsList(); 63 64 public interface Callbacks { 65 public int getCurrentWorkspaceScreen(); 66 public void startBinding(); 67 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); 68 public void finishBindingItems(); 69 public void bindAppWidget(LauncherAppWidgetInfo info); 70 public void bindAllApplications(ArrayList<ApplicationInfo> apps); 71 public void bindPackageAdded(ArrayList<ApplicationInfo> apps); 72 public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps); 73 public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps); 74 } 75 76 77 /** 78 * Adds an item to the DB if it was not created previously, or move it to a new 79 * <container, screen, cellX, cellY> 80 */ 81 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 82 int screen, int cellX, int cellY) { 83 if (item.container == ItemInfo.NO_ID) { 84 // From all apps 85 addItemToDatabase(context, item, container, screen, cellX, cellY, false); 86 } else { 87 // From somewhere else 88 moveItemInDatabase(context, item, container, screen, cellX, cellY); 89 } 90 } 91 92 /** 93 * Move an item in the DB to a new <container, screen, cellX, cellY> 94 */ 95 static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, 96 int cellX, int cellY) { 97 item.container = container; 98 item.screen = screen; 99 item.cellX = cellX; 100 item.cellY = cellY; 101 102 final ContentValues values = new ContentValues(); 103 final ContentResolver cr = context.getContentResolver(); 104 105 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 106 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 107 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 108 values.put(LauncherSettings.Favorites.SCREEN, item.screen); 109 110 cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); 111 } 112 113 /** 114 * Returns true if the shortcuts already exists in the database. 115 * we identify a shortcut by its title and intent. 116 */ 117 static boolean shortcutExists(Context context, String title, Intent intent) { 118 final ContentResolver cr = context.getContentResolver(); 119 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, 120 new String[] { "title", "intent" }, "title=? and intent=?", 121 new String[] { title, intent.toUri(0) }, null); 122 boolean result = false; 123 try { 124 result = c.moveToFirst(); 125 } finally { 126 c.close(); 127 } 128 return result; 129 } 130 131 /** 132 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. 133 */ 134 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { 135 final ContentResolver cr = context.getContentResolver(); 136 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, 137 "_id=? and (itemType=? or itemType=?)", 138 new String[] { String.valueOf(id), 139 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER), 140 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null); 141 142 try { 143 if (c.moveToFirst()) { 144 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 145 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 146 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 147 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 148 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 149 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 150 151 FolderInfo folderInfo = null; 152 switch (c.getInt(itemTypeIndex)) { 153 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 154 folderInfo = findOrMakeUserFolder(folderList, id); 155 break; 156 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 157 folderInfo = findOrMakeLiveFolder(folderList, id); 158 break; 159 } 160 161 folderInfo.title = c.getString(titleIndex); 162 folderInfo.id = id; 163 folderInfo.container = c.getInt(containerIndex); 164 folderInfo.screen = c.getInt(screenIndex); 165 folderInfo.cellX = c.getInt(cellXIndex); 166 folderInfo.cellY = c.getInt(cellYIndex); 167 168 return folderInfo; 169 } 170 } finally { 171 c.close(); 172 } 173 174 return null; 175 } 176 177 /** 178 * Add an item to the database in a specified container. Sets the container, screen, cellX and 179 * cellY fields of the item. Also assigns an ID to the item. 180 */ 181 static void addItemToDatabase(Context context, ItemInfo item, long container, 182 int screen, int cellX, int cellY, boolean notify) { 183 item.container = container; 184 item.screen = screen; 185 item.cellX = cellX; 186 item.cellY = cellY; 187 188 final ContentValues values = new ContentValues(); 189 final ContentResolver cr = context.getContentResolver(); 190 191 item.onAddToDatabase(values); 192 193 Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : 194 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); 195 196 if (result != null) { 197 item.id = Integer.parseInt(result.getPathSegments().get(1)); 198 } 199 } 200 201 /** 202 * Update an item to the database in a specified container. 203 */ 204 static void updateItemInDatabase(Context context, ItemInfo item) { 205 final ContentValues values = new ContentValues(); 206 final ContentResolver cr = context.getContentResolver(); 207 208 item.onAddToDatabase(values); 209 210 cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); 211 } 212 213 /** 214 * Removes the specified item from the database 215 * @param context 216 * @param item 217 */ 218 static void deleteItemFromDatabase(Context context, ItemInfo item) { 219 final ContentResolver cr = context.getContentResolver(); 220 221 cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null); 222 } 223 224 /** 225 * Remove the contents of the specified folder from the database 226 */ 227 static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) { 228 final ContentResolver cr = context.getContentResolver(); 229 230 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); 231 cr.delete(LauncherSettings.Favorites.CONTENT_URI, 232 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 233 } 234 235 /** 236 * Set this as the current Launcher activity object for the loader. 237 */ 238 public void initialize(Callbacks callbacks) { 239 synchronized (mLock) { 240 mCallbacks = new WeakReference<Callbacks>(callbacks); 241 } 242 } 243 244 public void startLoader(Context context, boolean isLaunching) { 245 mLoader.startLoader(context, isLaunching); 246 } 247 248 public void stopLoader() { 249 mLoader.stopLoader(); 250 } 251 252 public void setWorkspaceDirty() { 253 mLoader.setWorkspaceDirty(); 254 } 255 256 /** 257 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 258 * ACTION_PACKAGE_CHANGED. 259 */ 260 public void onReceiveIntent(Context context, Intent intent) { 261 final String packageName = intent.getData().getSchemeSpecificPart(); 262 263 ArrayList<ApplicationInfo> added = null; 264 ArrayList<ApplicationInfo> removed = null; 265 ArrayList<ApplicationInfo> modified = null; 266 boolean update = false; 267 boolean remove = false; 268 269 synchronized (mLock) { 270 final String action = intent.getAction(); 271 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 272 273 if (packageName == null || packageName.length() == 0) { 274 // they sent us a bad intent 275 return; 276 } 277 278 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 279 mAllAppsList.updatePackage(context, packageName); 280 update = true; 281 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 282 if (!replacing) { 283 mAllAppsList.removePackage(packageName); 284 remove = true; 285 } 286 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 287 // later, we will update the package at this time 288 } else { 289 if (!replacing) { 290 mAllAppsList.addPackage(context, packageName); 291 } else { 292 mAllAppsList.updatePackage(context, packageName); 293 update = true; 294 } 295 } 296 297 if (mAllAppsList.added.size() > 0) { 298 added = mAllAppsList.added; 299 mAllAppsList.added = new ArrayList(); 300 } 301 if (mAllAppsList.removed.size() > 0) { 302 removed = mAllAppsList.removed; 303 mAllAppsList.removed = new ArrayList(); 304 for (ApplicationInfo info: removed) { 305 AppInfoCache.remove(info.intent.getComponent()); 306 } 307 } 308 if (mAllAppsList.modified.size() > 0) { 309 modified = mAllAppsList.modified; 310 mAllAppsList.modified = new ArrayList(); 311 } 312 313 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 314 if (callbacks == null) { 315 return; 316 } 317 318 if (added != null) { 319 final ArrayList<ApplicationInfo> addedFinal = added; 320 mHandler.post(new Runnable() { 321 public void run() { 322 callbacks.bindPackageAdded(addedFinal); 323 } 324 }); 325 } 326 if (update || modified != null) { 327 final ArrayList<ApplicationInfo> modifiedFinal = modified; 328 mHandler.post(new Runnable() { 329 public void run() { 330 callbacks.bindPackageUpdated(packageName, modifiedFinal); 331 } 332 }); 333 } 334 if (remove || removed != null) { 335 final ArrayList<ApplicationInfo> removedFinal = removed; 336 mHandler.post(new Runnable() { 337 public void run() { 338 callbacks.bindPackageRemoved(packageName, removedFinal); 339 } 340 }); 341 } 342 } 343 } 344 345 public class Loader { 346 private static final int ITEMS_CHUNK = 6; 347 348 private LoaderThread mLoaderThread; 349 350 private int mLastWorkspaceSeq = 0; 351 private int mWorkspaceSeq = 1; 352 353 private int mLastAllAppsSeq = 0; 354 private int mAllAppsSeq = 1; 355 356 final ArrayList<ItemInfo> mItems = new ArrayList(); 357 final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList(); 358 final HashMap<Long, FolderInfo> folders = new HashMap(); 359 360 /** 361 * Call this from the ui thread so the handler is initialized on the correct thread. 362 */ 363 public Loader() { 364 } 365 366 public void startLoader(Context context, boolean isLaunching) { 367 synchronized (mLock) { 368 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 369 // Don't bother to start the thread if we know it's not going to do anything 370 if (mCallbacks.get() != null) { 371 LoaderThread oldThread = mLoaderThread; 372 if (oldThread != null) { 373 if (oldThread.isLaunching()) { 374 // don't downgrade isLaunching if we're already running 375 isLaunching = true; 376 } 377 oldThread.stopLocked(); 378 } 379 mLoaderThread = new LoaderThread(context, oldThread, isLaunching); 380 mLoaderThread.start(); 381 } 382 } 383 } 384 385 public void stopLoader() { 386 synchronized (mLock) { 387 if (mLoaderThread != null) { 388 mLoaderThread.stopLocked(); 389 } 390 } 391 } 392 393 public void setWorkspaceDirty() { 394 synchronized (mLock) { 395 mWorkspaceSeq++; 396 } 397 } 398 399 public void setAllAppsDirty() { 400 synchronized (mLock) { 401 mAllAppsSeq++; 402 } 403 } 404 405 /** 406 * Runnable for the thread that loads the contents of the launcher: 407 * - workspace icons 408 * - widgets 409 * - all apps icons 410 */ 411 private class LoaderThread extends Thread { 412 private Context mContext; 413 private Thread mWaitThread; 414 private boolean mIsLaunching; 415 private boolean mStopped; 416 private boolean mWorkspaceDoneBinding; 417 418 LoaderThread(Context context, Thread waitThread, boolean isLaunching) { 419 mContext = context; 420 mWaitThread = waitThread; 421 mIsLaunching = isLaunching; 422 } 423 424 boolean isLaunching() { 425 return mIsLaunching; 426 } 427 428 /** 429 * If another LoaderThread was supplied, we need to wait for that to finish before 430 * we start our processing. This keeps the ordering of the setting and clearing 431 * of the dirty flags correct by making sure we don't start processing stuff until 432 * they've had a chance to re-set them. We do this waiting the worker thread, not 433 * the ui thread to avoid ANRs. 434 */ 435 private void waitForOtherThread() { 436 if (mWaitThread != null) { 437 boolean done = false; 438 while (!done) { 439 try { 440 mWaitThread.join(); 441 done = true; 442 } catch (InterruptedException ex) { 443 } 444 } 445 mWaitThread = null; 446 } 447 } 448 449 public void run() { 450 waitForOtherThread(); 451 452 // Elevate priority when Home launches for the first time to avoid 453 // starving at boot time. Staring at a blank home is not cool. 454 synchronized (mLock) { 455 android.os.Process.setThreadPriority(mIsLaunching 456 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 457 } 458 459 // Load the workspace only if it's dirty. 460 int workspaceSeq; 461 boolean workspaceDirty; 462 synchronized (mLock) { 463 workspaceSeq = mWorkspaceSeq; 464 workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq; 465 } 466 if (workspaceDirty) { 467 loadWorkspace(); 468 } 469 synchronized (mLock) { 470 // If we're not stopped, and nobody has incremented mWorkspaceSeq. 471 if (mStopped) { 472 return; 473 } 474 if (workspaceSeq == mWorkspaceSeq) { 475 mLastWorkspaceSeq = mWorkspaceSeq; 476 } 477 } 478 479 // Bind the workspace 480 bindWorkspace(); 481 482 // Wait until the either we're stopped or the other threads are done. 483 // This way we don't start loading all apps until the workspace has settled 484 // down. 485 synchronized (LoaderThread.this) { 486 mHandler.post(new Runnable() { 487 public void run() { 488 synchronized (LoaderThread.this) { 489 mWorkspaceDoneBinding = true; 490 Log.d(TAG, "done with workspace"); 491 LoaderThread.this.notify(); 492 } 493 } 494 }); 495 Log.d(TAG, "waiting to be done with workspace"); 496 while (!mStopped && !mWorkspaceDoneBinding) { 497 try { 498 this.wait(); 499 } catch (InterruptedException ex) { 500 } 501 } 502 Log.d(TAG, "done waiting to be done with workspace"); 503 } 504 505 // Load all apps if they're dirty 506 int allAppsSeq; 507 boolean allAppsDirty; 508 synchronized (mLock) { 509 allAppsSeq = mAllAppsSeq; 510 allAppsDirty = mAllAppsSeq != mLastAllAppsSeq; 511 } 512 if (allAppsDirty) { 513 loadAllApps(); 514 } 515 synchronized (mLock) { 516 // If we're not stopped, and nobody has incremented mAllAppsSeq. 517 if (mStopped) { 518 return; 519 } 520 if (allAppsSeq == mAllAppsSeq) { 521 mLastAllAppsSeq = mAllAppsSeq; 522 } 523 } 524 525 // Bind all apps 526 if (allAppsDirty) { 527 bindAllApps(); 528 } 529 530 // Clear out this reference, otherwise we end up holding it until all of the 531 // callback runnables are done. 532 mContext = null; 533 534 synchronized (mLock) { 535 // Setting the reference is atomic, but we can't do it inside the other critical 536 // sections. 537 mLoaderThread = null; 538 return; 539 } 540 } 541 542 public void stopLocked() { 543 synchronized (LoaderThread.this) { 544 mStopped = true; 545 this.notify(); 546 } 547 } 548 549 /** 550 * Gets the callbacks object. If we've been stopped, or if the launcher object 551 * has somehow been garbage collected, return null instead. 552 */ 553 Callbacks tryGetCallbacks() { 554 synchronized (mLock) { 555 if (mStopped) { 556 return null; 557 } 558 559 final Callbacks callbacks = mCallbacks.get(); 560 if (callbacks == null) { 561 Log.w(TAG, "no mCallbacks"); 562 return null; 563 } 564 565 return callbacks; 566 } 567 } 568 569 private void loadWorkspace() { 570 long t = SystemClock.uptimeMillis(); 571 572 final Context context = mContext; 573 final ContentResolver contentResolver = context.getContentResolver(); 574 final PackageManager manager = context.getPackageManager(); 575 576 /* TODO 577 if (mLocaleChanged) { 578 updateShortcutLabels(contentResolver, manager); 579 } 580 */ 581 582 final Cursor c = contentResolver.query( 583 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); 584 585 try { 586 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 587 final int intentIndex = c.getColumnIndexOrThrow 588 (LauncherSettings.Favorites.INTENT); 589 final int titleIndex = c.getColumnIndexOrThrow 590 (LauncherSettings.Favorites.TITLE); 591 final int iconTypeIndex = c.getColumnIndexOrThrow( 592 LauncherSettings.Favorites.ICON_TYPE); 593 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 594 final int iconPackageIndex = c.getColumnIndexOrThrow( 595 LauncherSettings.Favorites.ICON_PACKAGE); 596 final int iconResourceIndex = c.getColumnIndexOrThrow( 597 LauncherSettings.Favorites.ICON_RESOURCE); 598 final int containerIndex = c.getColumnIndexOrThrow( 599 LauncherSettings.Favorites.CONTAINER); 600 final int itemTypeIndex = c.getColumnIndexOrThrow( 601 LauncherSettings.Favorites.ITEM_TYPE); 602 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 603 LauncherSettings.Favorites.APPWIDGET_ID); 604 final int screenIndex = c.getColumnIndexOrThrow( 605 LauncherSettings.Favorites.SCREEN); 606 final int cellXIndex = c.getColumnIndexOrThrow 607 (LauncherSettings.Favorites.CELLX); 608 final int cellYIndex = c.getColumnIndexOrThrow 609 (LauncherSettings.Favorites.CELLY); 610 final int spanXIndex = c.getColumnIndexOrThrow 611 (LauncherSettings.Favorites.SPANX); 612 final int spanYIndex = c.getColumnIndexOrThrow( 613 LauncherSettings.Favorites.SPANY); 614 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 615 final int displayModeIndex = c.getColumnIndexOrThrow( 616 LauncherSettings.Favorites.DISPLAY_MODE); 617 618 ApplicationInfo info; 619 String intentDescription; 620 Widget widgetInfo; 621 LauncherAppWidgetInfo appWidgetInfo; 622 int container; 623 long id; 624 Intent intent; 625 626 while (!mStopped && c.moveToNext()) { 627 try { 628 int itemType = c.getInt(itemTypeIndex); 629 630 switch (itemType) { 631 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 632 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 633 intentDescription = c.getString(intentIndex); 634 try { 635 intent = Intent.parseUri(intentDescription, 0); 636 } catch (URISyntaxException e) { 637 continue; 638 } 639 640 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 641 info = getApplicationInfo(manager, intent, context); 642 } else { 643 info = getApplicationInfoShortcut(c, context, iconTypeIndex, 644 iconPackageIndex, iconResourceIndex, iconIndex); 645 } 646 647 if (info == null) { 648 info = new ApplicationInfo(); 649 info.icon = manager.getDefaultActivityIcon(); 650 } 651 652 if (info != null) { 653 info.title = c.getString(titleIndex); 654 info.intent = intent; 655 656 info.id = c.getLong(idIndex); 657 container = c.getInt(containerIndex); 658 info.container = container; 659 info.screen = c.getInt(screenIndex); 660 info.cellX = c.getInt(cellXIndex); 661 info.cellY = c.getInt(cellYIndex); 662 663 switch (container) { 664 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 665 mItems.add(info); 666 break; 667 default: 668 // Item is in a user folder 669 UserFolderInfo folderInfo = 670 findOrMakeUserFolder(folders, container); 671 folderInfo.add(info); 672 break; 673 } 674 } 675 break; 676 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 677 678 id = c.getLong(idIndex); 679 UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id); 680 681 folderInfo.title = c.getString(titleIndex); 682 683 folderInfo.id = id; 684 container = c.getInt(containerIndex); 685 folderInfo.container = container; 686 folderInfo.screen = c.getInt(screenIndex); 687 folderInfo.cellX = c.getInt(cellXIndex); 688 folderInfo.cellY = c.getInt(cellYIndex); 689 690 switch (container) { 691 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 692 mItems.add(folderInfo); 693 break; 694 } 695 break; 696 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 697 698 id = c.getLong(idIndex); 699 LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id); 700 701 intentDescription = c.getString(intentIndex); 702 intent = null; 703 if (intentDescription != null) { 704 try { 705 intent = Intent.parseUri(intentDescription, 0); 706 } catch (URISyntaxException e) { 707 // Ignore, a live folder might not have a base intent 708 } 709 } 710 711 liveFolderInfo.title = c.getString(titleIndex); 712 liveFolderInfo.id = id; 713 container = c.getInt(containerIndex); 714 liveFolderInfo.container = container; 715 liveFolderInfo.screen = c.getInt(screenIndex); 716 liveFolderInfo.cellX = c.getInt(cellXIndex); 717 liveFolderInfo.cellY = c.getInt(cellYIndex); 718 liveFolderInfo.uri = Uri.parse(c.getString(uriIndex)); 719 liveFolderInfo.baseIntent = intent; 720 liveFolderInfo.displayMode = c.getInt(displayModeIndex); 721 722 loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex, 723 iconResourceIndex, liveFolderInfo); 724 725 switch (container) { 726 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 727 mItems.add(liveFolderInfo); 728 break; 729 } 730 break; 731 case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: 732 widgetInfo = Widget.makeSearch(); 733 734 container = c.getInt(containerIndex); 735 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 736 Log.e(TAG, "Widget found where container " 737 + "!= CONTAINER_DESKTOP ignoring!"); 738 continue; 739 } 740 741 widgetInfo.id = c.getLong(idIndex); 742 widgetInfo.screen = c.getInt(screenIndex); 743 widgetInfo.container = container; 744 widgetInfo.cellX = c.getInt(cellXIndex); 745 widgetInfo.cellY = c.getInt(cellYIndex); 746 747 mItems.add(widgetInfo); 748 break; 749 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 750 // Read all Launcher-specific widget details 751 int appWidgetId = c.getInt(appWidgetIdIndex); 752 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); 753 appWidgetInfo.id = c.getLong(idIndex); 754 appWidgetInfo.screen = c.getInt(screenIndex); 755 appWidgetInfo.cellX = c.getInt(cellXIndex); 756 appWidgetInfo.cellY = c.getInt(cellYIndex); 757 appWidgetInfo.spanX = c.getInt(spanXIndex); 758 appWidgetInfo.spanY = c.getInt(spanYIndex); 759 760 container = c.getInt(containerIndex); 761 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 762 Log.e(TAG, "Widget found where container " 763 + "!= CONTAINER_DESKTOP -- ignoring!"); 764 continue; 765 } 766 appWidgetInfo.container = c.getInt(containerIndex); 767 768 mAppWidgets.add(appWidgetInfo); 769 break; 770 } 771 } catch (Exception e) { 772 Log.w(TAG, "Desktop items loading interrupted:", e); 773 } 774 } 775 } finally { 776 c.close(); 777 } 778 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 779 } 780 781 /** 782 * Read everything out of our database. 783 */ 784 private void bindWorkspace() { 785 final long t = SystemClock.uptimeMillis(); 786 787 // Don't use these two variables in any of the callback runnables. 788 // Otherwise we hold a reference to them. 789 Callbacks callbacks = mCallbacks.get(); 790 if (callbacks == null) { 791 // This launcher has exited and nobody bothered to tell us. Just bail. 792 Log.w(TAG, "LoaderThread running with no launcher"); 793 return; 794 } 795 796 int N; 797 // Tell the workspace that we're about to start firing items at it 798 mHandler.post(new Runnable() { 799 public void run() { 800 Callbacks callbacks = tryGetCallbacks(); 801 if (callbacks != null) { 802 callbacks.startBinding(); 803 } 804 } 805 }); 806 // Add the items to the workspace. 807 N = mItems.size(); 808 for (int i=0; i<N; i+=ITEMS_CHUNK) { 809 final int start = i; 810 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 811 mHandler.post(new Runnable() { 812 public void run() { 813 Callbacks callbacks = tryGetCallbacks(); 814 if (callbacks != null) { 815 callbacks.bindItems(mItems, start, start+chunkSize); 816 } 817 } 818 }); 819 } 820 // Wait until the queue goes empty. 821 mHandler.postIdle(new Runnable() { 822 public void run() { 823 Log.d(TAG, "Going to start binding widgets soon."); 824 } 825 }); 826 // Bind the widgets, one at a time. 827 // WARNING: this is calling into the workspace from the background thread, 828 // but since getCurrentScreen() just returns the int, we should be okay. This 829 // is just a hint for the order, and if it's wrong, we'll be okay. 830 // TODO: instead, we should have that push the current screen into here. 831 final int currentScreen = callbacks.getCurrentWorkspaceScreen(); 832 N = mAppWidgets.size(); 833 // once for the current screen 834 for (int i=0; i<N; i++) { 835 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 836 if (widget.screen == currentScreen) { 837 mHandler.post(new Runnable() { 838 public void run() { 839 Callbacks callbacks = tryGetCallbacks(); 840 if (callbacks != null) { 841 callbacks.bindAppWidget(widget); 842 } 843 } 844 }); 845 } 846 } 847 // once for the other screens 848 for (int i=0; i<N; i++) { 849 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 850 if (widget.screen != currentScreen) { 851 mHandler.post(new Runnable() { 852 public void run() { 853 Callbacks callbacks = tryGetCallbacks(); 854 if (callbacks != null) { 855 callbacks.bindAppWidget(widget); 856 } 857 } 858 }); 859 } 860 } 861 // TODO: Bind the folders 862 // Tell the workspace that we're done. 863 mHandler.post(new Runnable() { 864 public void run() { 865 Callbacks callbacks = tryGetCallbacks(); 866 if (callbacks != null) { 867 callbacks.finishBindingItems(); 868 } 869 } 870 }); 871 // If we're profiling, this is the last thing in the queue. 872 mHandler.post(new Runnable() { 873 public void run() { 874 Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 875 if (Launcher.PROFILE_ROTATE) { 876 android.os.Debug.stopMethodTracing(); 877 } 878 } 879 }); 880 } 881 882 private void loadAllApps() { 883 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 884 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 885 886 final Callbacks callbacks = tryGetCallbacks(); 887 if (callbacks == null) { 888 return; 889 } 890 891 final Context context = mContext; 892 final PackageManager packageManager = context.getPackageManager(); 893 894 final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); 895 896 synchronized (mLock) { 897 mAllAppsList.clear(); 898 if (apps != null) { 899 long t = SystemClock.uptimeMillis(); 900 901 int N = apps.size(); 902 Utilities.BubbleText bubble = new Utilities.BubbleText(context); 903 for (int i=0; i<N && !mStopped; i++) { 904 // This builds the icon bitmaps. 905 mAllAppsList.add(AppInfoCache.cache(apps.get(i), context, bubble)); 906 } 907 Collections.sort(mAllAppsList.data, sComparator); 908 Collections.sort(mAllAppsList.added, sComparator); 909 Log.d(TAG, "cached app icons in " + (SystemClock.uptimeMillis()-t) + "ms"); 910 } 911 } 912 } 913 914 private void bindAllApps() { 915 synchronized (mLock) { 916 final ArrayList<ApplicationInfo> results = mAllAppsList.added; 917 mAllAppsList.added = new ArrayList(); 918 mHandler.post(new Runnable() { 919 public void run() { 920 final long t = SystemClock.uptimeMillis(); 921 final int count = results.size(); 922 923 Callbacks callbacks = tryGetCallbacks(); 924 if (callbacks != null) { 925 callbacks.bindAllApplications(results); 926 } 927 928 Log.d(TAG, "bound app " + count + " icons in " 929 + (SystemClock.uptimeMillis()-t) + "ms"); 930 } 931 }); 932 } 933 } 934 } 935 } 936 937 /** 938 * Make an ApplicationInfo object for an application. 939 */ 940 private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent, 941 Context context) { 942 final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0); 943 944 if (resolveInfo == null) { 945 return null; 946 } 947 948 final ApplicationInfo info = new ApplicationInfo(); 949 final ActivityInfo activityInfo = resolveInfo.activityInfo; 950 info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context); 951 if (info.title == null || info.title.length() == 0) { 952 info.title = activityInfo.loadLabel(manager); 953 } 954 if (info.title == null) { 955 info.title = ""; 956 } 957 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 958 return info; 959 } 960 961 /** 962 * Make an ApplicationInfo object for a sortcut 963 */ 964 private static ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context, 965 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) { 966 967 final ApplicationInfo info = new ApplicationInfo(); 968 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 969 970 int iconType = c.getInt(iconTypeIndex); 971 switch (iconType) { 972 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 973 String packageName = c.getString(iconPackageIndex); 974 String resourceName = c.getString(iconResourceIndex); 975 PackageManager packageManager = context.getPackageManager(); 976 try { 977 Resources resources = packageManager.getResourcesForApplication(packageName); 978 final int id = resources.getIdentifier(resourceName, null, null); 979 info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context); 980 } catch (Exception e) { 981 info.icon = packageManager.getDefaultActivityIcon(); 982 } 983 info.iconResource = new Intent.ShortcutIconResource(); 984 info.iconResource.packageName = packageName; 985 info.iconResource.resourceName = resourceName; 986 info.customIcon = false; 987 break; 988 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 989 byte[] data = c.getBlob(iconIndex); 990 try { 991 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 992 info.icon = new FastBitmapDrawable( 993 Utilities.createBitmapThumbnail(bitmap, context)); 994 } catch (Exception e) { 995 packageManager = context.getPackageManager(); 996 info.icon = packageManager.getDefaultActivityIcon(); 997 } 998 info.filtered = true; 999 info.customIcon = true; 1000 break; 1001 default: 1002 info.icon = context.getPackageManager().getDefaultActivityIcon(); 1003 info.customIcon = false; 1004 break; 1005 } 1006 return info; 1007 } 1008 1009 private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, 1010 int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { 1011 1012 int iconType = c.getInt(iconTypeIndex); 1013 switch (iconType) { 1014 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 1015 String packageName = c.getString(iconPackageIndex); 1016 String resourceName = c.getString(iconResourceIndex); 1017 PackageManager packageManager = context.getPackageManager(); 1018 try { 1019 Resources resources = packageManager.getResourcesForApplication(packageName); 1020 final int id = resources.getIdentifier(resourceName, null, null); 1021 liveFolderInfo.icon = resources.getDrawable(id); 1022 } catch (Exception e) { 1023 liveFolderInfo.icon = 1024 context.getResources().getDrawable(R.drawable.ic_launcher_folder); 1025 } 1026 liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); 1027 liveFolderInfo.iconResource.packageName = packageName; 1028 liveFolderInfo.iconResource.resourceName = resourceName; 1029 break; 1030 default: 1031 liveFolderInfo.icon = 1032 context.getResources().getDrawable(R.drawable.ic_launcher_folder); 1033 } 1034 } 1035 1036 /** 1037 * Return an existing UserFolderInfo object if we have encountered this ID previously, 1038 * or make a new one. 1039 */ 1040 private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) { 1041 // See if a placeholder was created for us already 1042 FolderInfo folderInfo = folders.get(id); 1043 if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) { 1044 // No placeholder -- create a new instance 1045 folderInfo = new UserFolderInfo(); 1046 folders.put(id, folderInfo); 1047 } 1048 return (UserFolderInfo) folderInfo; 1049 } 1050 1051 /** 1052 * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a 1053 * new one. 1054 */ 1055 private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) { 1056 // See if a placeholder was created for us already 1057 FolderInfo folderInfo = folders.get(id); 1058 if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) { 1059 // No placeholder -- create a new instance 1060 folderInfo = new LiveFolderInfo(); 1061 folders.put(id, folderInfo); 1062 } 1063 return (LiveFolderInfo) folderInfo; 1064 } 1065 1066 private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { 1067 final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI, 1068 new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE, 1069 LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE }, 1070 null, null, null); 1071 1072 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1073 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1074 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1075 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1076 1077 // boolean changed = false; 1078 1079 try { 1080 while (c.moveToNext()) { 1081 try { 1082 if (c.getInt(itemTypeIndex) != 1083 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1084 continue; 1085 } 1086 1087 final String intentUri = c.getString(intentIndex); 1088 if (intentUri != null) { 1089 final Intent shortcut = Intent.parseUri(intentUri, 0); 1090 if (Intent.ACTION_MAIN.equals(shortcut.getAction())) { 1091 final ComponentName name = shortcut.getComponent(); 1092 if (name != null) { 1093 final ActivityInfo activityInfo = manager.getActivityInfo(name, 0); 1094 final String title = c.getString(titleIndex); 1095 String label = getLabel(manager, activityInfo); 1096 1097 if (title == null || !title.equals(label)) { 1098 final ContentValues values = new ContentValues(); 1099 values.put(LauncherSettings.Favorites.TITLE, label); 1100 1101 resolver.update( 1102 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, 1103 values, "_id=?", 1104 new String[] { String.valueOf(c.getLong(idIndex)) }); 1105 1106 // changed = true; 1107 } 1108 } 1109 } 1110 } 1111 } catch (URISyntaxException e) { 1112 // Ignore 1113 } catch (PackageManager.NameNotFoundException e) { 1114 // Ignore 1115 } 1116 } 1117 } finally { 1118 c.close(); 1119 } 1120 1121 // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null); 1122 } 1123 1124 private static String getLabel(PackageManager manager, ActivityInfo activityInfo) { 1125 String label = activityInfo.loadLabel(manager).toString(); 1126 if (label == null) { 1127 label = manager.getApplicationLabel(activityInfo.applicationInfo).toString(); 1128 if (label == null) { 1129 label = activityInfo.name; 1130 } 1131 } 1132 return label; 1133 } 1134 1135 private static final Collator sCollator = Collator.getInstance(); 1136 private static final Comparator<ApplicationInfo> sComparator 1137 = new Comparator<ApplicationInfo>() { 1138 public final int compare(ApplicationInfo a, ApplicationInfo b) { 1139 return sCollator.compare(a.title.toString(), b.title.toString()); 1140 } 1141 }; 1142} 1143