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