LauncherModel.java revision b0c27f254a9929be208d5e04554f438076c833bc
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 = false; 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 // Use the app as the context. 274 context = mApp; 275 276 final String packageName = intent.getData().getSchemeSpecificPart(); 277 278 ArrayList<ApplicationInfo> added = null; 279 ArrayList<ApplicationInfo> removed = null; 280 ArrayList<ApplicationInfo> modified = null; 281 282 synchronized (mLock) { 283 if (mBeforeFirstLoad) { 284 // If we haven't even loaded yet, don't bother, since we'll just pick 285 // up the changes. 286 return; 287 } 288 289 final String action = intent.getAction(); 290 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 291 292 if (packageName == null || packageName.length() == 0) { 293 // they sent us a bad intent 294 return; 295 } 296 297 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 298 mAllAppsList.updatePackage(context, packageName); 299 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 300 if (!replacing) { 301 mAllAppsList.removePackage(packageName); 302 } 303 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 304 // later, we will update the package at this time 305 } else { 306 if (!replacing) { 307 mAllAppsList.addPackage(context, packageName); 308 } else { 309 mAllAppsList.updatePackage(context, packageName); 310 } 311 } 312 313 if (mAllAppsList.added.size() > 0) { 314 added = mAllAppsList.added; 315 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 316 } 317 if (mAllAppsList.removed.size() > 0) { 318 removed = mAllAppsList.removed; 319 mAllAppsList.removed = new ArrayList<ApplicationInfo>(); 320 for (ApplicationInfo info: removed) { 321 AppInfoCache.remove(info.intent.getComponent()); 322 } 323 } 324 if (mAllAppsList.modified.size() > 0) { 325 modified = mAllAppsList.modified; 326 mAllAppsList.modified = new ArrayList<ApplicationInfo>(); 327 } 328 329 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 330 if (callbacks == null) { 331 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); 332 return; 333 } 334 335 if (added != null) { 336 final ArrayList<ApplicationInfo> addedFinal = added; 337 mHandler.post(new Runnable() { 338 public void run() { 339 callbacks.bindPackageAdded(addedFinal); 340 } 341 }); 342 } 343 if (modified != null) { 344 final ArrayList<ApplicationInfo> modifiedFinal = modified; 345 mHandler.post(new Runnable() { 346 public void run() { 347 callbacks.bindPackageUpdated(packageName, modifiedFinal); 348 } 349 }); 350 } 351 if (removed != null) { 352 final ArrayList<ApplicationInfo> removedFinal = removed; 353 mHandler.post(new Runnable() { 354 public void run() { 355 callbacks.bindPackageRemoved(packageName, removedFinal); 356 } 357 }); 358 } 359 } 360 } 361 362 public class Loader { 363 private static final int ITEMS_CHUNK = 6; 364 365 private LoaderThread mLoaderThread; 366 367 private int mLastWorkspaceSeq = 0; 368 private int mWorkspaceSeq = 1; 369 370 private int mLastAllAppsSeq = 0; 371 private int mAllAppsSeq = 1; 372 373 final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); 374 final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); 375 final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>(); 376 377 /** 378 * Call this from the ui thread so the handler is initialized on the correct thread. 379 */ 380 public Loader() { 381 } 382 383 public void startLoader(Context context, boolean isLaunching) { 384 synchronized (mLock) { 385 if (DEBUG_LOADERS) { 386 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 387 } 388 // Don't bother to start the thread if we know it's not going to do anything 389 if (mCallbacks.get() != null) { 390 LoaderThread oldThread = mLoaderThread; 391 if (oldThread != null) { 392 if (oldThread.isLaunching()) { 393 // don't downgrade isLaunching if we're already running 394 isLaunching = true; 395 } 396 oldThread.stopLocked(); 397 } 398 mLoaderThread = new LoaderThread(context, oldThread, isLaunching); 399 mLoaderThread.start(); 400 } 401 } 402 } 403 404 public void stopLoader() { 405 synchronized (mLock) { 406 if (mLoaderThread != null) { 407 mLoaderThread.stopLocked(); 408 } 409 } 410 } 411 412 public void setWorkspaceDirty() { 413 synchronized (mLock) { 414 mWorkspaceSeq++; 415 } 416 } 417 418 public void setAllAppsDirty() { 419 synchronized (mLock) { 420 mAllAppsSeq++; 421 } 422 } 423 424 /** 425 * Runnable for the thread that loads the contents of the launcher: 426 * - workspace icons 427 * - widgets 428 * - all apps icons 429 */ 430 private class LoaderThread extends Thread { 431 private Context mContext; 432 private Thread mWaitThread; 433 private boolean mIsLaunching; 434 private boolean mStopped; 435 private boolean mWorkspaceDoneBinding; 436 437 LoaderThread(Context context, Thread waitThread, boolean isLaunching) { 438 mContext = context; 439 mWaitThread = waitThread; 440 mIsLaunching = isLaunching; 441 } 442 443 boolean isLaunching() { 444 return mIsLaunching; 445 } 446 447 /** 448 * If another LoaderThread was supplied, we need to wait for that to finish before 449 * we start our processing. This keeps the ordering of the setting and clearing 450 * of the dirty flags correct by making sure we don't start processing stuff until 451 * they've had a chance to re-set them. We do this waiting the worker thread, not 452 * the ui thread to avoid ANRs. 453 */ 454 private void waitForOtherThread() { 455 if (mWaitThread != null) { 456 boolean done = false; 457 while (!done) { 458 try { 459 mWaitThread.join(); 460 done = true; 461 } catch (InterruptedException ex) { 462 // Ignore 463 } 464 } 465 mWaitThread = null; 466 } 467 } 468 469 public void run() { 470 waitForOtherThread(); 471 472 // Elevate priority when Home launches for the first time to avoid 473 // starving at boot time. Staring at a blank home is not cool. 474 synchronized (mLock) { 475 android.os.Process.setThreadPriority(mIsLaunching 476 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 477 } 478 479 // Load the workspace only if it's dirty. 480 int workspaceSeq; 481 boolean workspaceDirty; 482 synchronized (mLock) { 483 workspaceSeq = mWorkspaceSeq; 484 workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq; 485 } 486 if (workspaceDirty) { 487 loadWorkspace(); 488 } 489 synchronized (mLock) { 490 // If we're not stopped, and nobody has incremented mWorkspaceSeq. 491 if (mStopped) { 492 return; 493 } 494 if (workspaceSeq == mWorkspaceSeq) { 495 mLastWorkspaceSeq = mWorkspaceSeq; 496 } 497 } 498 499 // Bind the workspace 500 bindWorkspace(); 501 502 // Wait until the either we're stopped or the other threads are done. 503 // This way we don't start loading all apps until the workspace has settled 504 // down. 505 synchronized (LoaderThread.this) { 506 mHandler.postIdle(new Runnable() { 507 public void run() { 508 synchronized (LoaderThread.this) { 509 mWorkspaceDoneBinding = true; 510 if (DEBUG_LOADERS) { 511 Log.d(TAG, "done with workspace"); 512 } 513 LoaderThread.this.notify(); 514 } 515 } 516 }); 517 if (DEBUG_LOADERS) { 518 Log.d(TAG, "waiting to be done with workspace"); 519 } 520 while (!mStopped && !mWorkspaceDoneBinding) { 521 try { 522 this.wait(); 523 } catch (InterruptedException ex) { 524 // Ignore 525 } 526 } 527 if (DEBUG_LOADERS) { 528 Log.d(TAG, "done waiting to be done with workspace"); 529 } 530 } 531 532 // Load all apps if they're dirty 533 int allAppsSeq; 534 boolean allAppsDirty; 535 synchronized (mLock) { 536 allAppsSeq = mAllAppsSeq; 537 allAppsDirty = mAllAppsSeq != mLastAllAppsSeq; 538 if (DEBUG_LOADERS) { 539 Log.d(TAG, "mAllAppsSeq=" + mAllAppsSeq 540 + " mLastAllAppsSeq=" + mLastAllAppsSeq + " allAppsDirty"); 541 } 542 } 543 if (allAppsDirty) { 544 loadAllApps(); 545 } 546 synchronized (mLock) { 547 // If we're not stopped, and nobody has incremented mAllAppsSeq. 548 if (mStopped) { 549 return; 550 } 551 if (allAppsSeq == mAllAppsSeq) { 552 mLastAllAppsSeq = mAllAppsSeq; 553 } 554 } 555 556 // Bind all apps 557 if (allAppsDirty) { 558 bindAllApps(); 559 } 560 561 // Clear out this reference, otherwise we end up holding it until all of the 562 // callback runnables are done. 563 mContext = null; 564 565 synchronized (mLock) { 566 // Setting the reference is atomic, but we can't do it inside the other critical 567 // sections. 568 mLoaderThread = null; 569 } 570 } 571 572 public void stopLocked() { 573 synchronized (LoaderThread.this) { 574 mStopped = true; 575 this.notify(); 576 } 577 } 578 579 /** 580 * Gets the callbacks object. If we've been stopped, or if the launcher object 581 * has somehow been garbage collected, return null instead. 582 */ 583 Callbacks tryGetCallbacks() { 584 synchronized (mLock) { 585 if (mStopped) { 586 return null; 587 } 588 589 final Callbacks callbacks = mCallbacks.get(); 590 if (callbacks == null) { 591 Log.w(TAG, "no mCallbacks"); 592 return null; 593 } 594 595 return callbacks; 596 } 597 } 598 599 private void loadWorkspace() { 600 long t = SystemClock.uptimeMillis(); 601 602 final Context context = mContext; 603 final ContentResolver contentResolver = context.getContentResolver(); 604 final PackageManager manager = context.getPackageManager(); 605 606 /* TODO 607 if (mLocaleChanged) { 608 updateShortcutLabels(contentResolver, manager); 609 } 610 */ 611 612 mItems.clear(); 613 mAppWidgets.clear(); 614 mFolders.clear(); 615 616 final Cursor c = contentResolver.query( 617 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); 618 619 try { 620 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 621 final int intentIndex = c.getColumnIndexOrThrow 622 (LauncherSettings.Favorites.INTENT); 623 final int titleIndex = c.getColumnIndexOrThrow 624 (LauncherSettings.Favorites.TITLE); 625 final int iconTypeIndex = c.getColumnIndexOrThrow( 626 LauncherSettings.Favorites.ICON_TYPE); 627 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 628 final int iconPackageIndex = c.getColumnIndexOrThrow( 629 LauncherSettings.Favorites.ICON_PACKAGE); 630 final int iconResourceIndex = c.getColumnIndexOrThrow( 631 LauncherSettings.Favorites.ICON_RESOURCE); 632 final int containerIndex = c.getColumnIndexOrThrow( 633 LauncherSettings.Favorites.CONTAINER); 634 final int itemTypeIndex = c.getColumnIndexOrThrow( 635 LauncherSettings.Favorites.ITEM_TYPE); 636 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 637 LauncherSettings.Favorites.APPWIDGET_ID); 638 final int screenIndex = c.getColumnIndexOrThrow( 639 LauncherSettings.Favorites.SCREEN); 640 final int cellXIndex = c.getColumnIndexOrThrow 641 (LauncherSettings.Favorites.CELLX); 642 final int cellYIndex = c.getColumnIndexOrThrow 643 (LauncherSettings.Favorites.CELLY); 644 final int spanXIndex = c.getColumnIndexOrThrow 645 (LauncherSettings.Favorites.SPANX); 646 final int spanYIndex = c.getColumnIndexOrThrow( 647 LauncherSettings.Favorites.SPANY); 648 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 649 final int displayModeIndex = c.getColumnIndexOrThrow( 650 LauncherSettings.Favorites.DISPLAY_MODE); 651 652 ApplicationInfo info; 653 String intentDescription; 654 Widget widgetInfo; 655 LauncherAppWidgetInfo appWidgetInfo; 656 int container; 657 long id; 658 Intent intent; 659 660 while (!mStopped && c.moveToNext()) { 661 try { 662 int itemType = c.getInt(itemTypeIndex); 663 664 switch (itemType) { 665 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 666 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 667 intentDescription = c.getString(intentIndex); 668 try { 669 intent = Intent.parseUri(intentDescription, 0); 670 } catch (URISyntaxException e) { 671 continue; 672 } 673 674 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 675 info = getApplicationInfo(manager, intent, context); 676 } else { 677 info = getApplicationInfoShortcut(c, context, iconTypeIndex, 678 iconPackageIndex, iconResourceIndex, iconIndex); 679 } 680 681 if (info == null) { 682 info = new ApplicationInfo(); 683 info.icon = manager.getDefaultActivityIcon(); 684 } 685 686 if (info != null) { 687 if (itemType 688 != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 689 info.title = c.getString(titleIndex); 690 } 691 info.intent = intent; 692 693 info.id = c.getLong(idIndex); 694 container = c.getInt(containerIndex); 695 info.container = container; 696 info.screen = c.getInt(screenIndex); 697 info.cellX = c.getInt(cellXIndex); 698 info.cellY = c.getInt(cellYIndex); 699 700 switch (container) { 701 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 702 mItems.add(info); 703 break; 704 default: 705 // Item is in a user folder 706 UserFolderInfo folderInfo = 707 findOrMakeUserFolder(mFolders, container); 708 folderInfo.add(info); 709 break; 710 } 711 } 712 break; 713 714 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 715 id = c.getLong(idIndex); 716 UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id); 717 718 folderInfo.title = c.getString(titleIndex); 719 720 folderInfo.id = id; 721 container = c.getInt(containerIndex); 722 folderInfo.container = container; 723 folderInfo.screen = c.getInt(screenIndex); 724 folderInfo.cellX = c.getInt(cellXIndex); 725 folderInfo.cellY = c.getInt(cellYIndex); 726 727 switch (container) { 728 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 729 mItems.add(folderInfo); 730 break; 731 } 732 733 mFolders.put(folderInfo.id, folderInfo); 734 break; 735 736 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 737 738 id = c.getLong(idIndex); 739 LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id); 740 741 intentDescription = c.getString(intentIndex); 742 intent = null; 743 if (intentDescription != null) { 744 try { 745 intent = Intent.parseUri(intentDescription, 0); 746 } catch (URISyntaxException e) { 747 // Ignore, a live folder might not have a base intent 748 } 749 } 750 751 liveFolderInfo.title = c.getString(titleIndex); 752 liveFolderInfo.id = id; 753 container = c.getInt(containerIndex); 754 liveFolderInfo.container = container; 755 liveFolderInfo.screen = c.getInt(screenIndex); 756 liveFolderInfo.cellX = c.getInt(cellXIndex); 757 liveFolderInfo.cellY = c.getInt(cellYIndex); 758 liveFolderInfo.uri = Uri.parse(c.getString(uriIndex)); 759 liveFolderInfo.baseIntent = intent; 760 liveFolderInfo.displayMode = c.getInt(displayModeIndex); 761 762 loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex, 763 iconResourceIndex, liveFolderInfo); 764 765 switch (container) { 766 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 767 mItems.add(liveFolderInfo); 768 break; 769 } 770 mFolders.put(liveFolderInfo.id, liveFolderInfo); 771 break; 772 773 case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: 774 widgetInfo = Widget.makeSearch(); 775 776 container = c.getInt(containerIndex); 777 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 778 Log.e(TAG, "Widget found where container " 779 + "!= CONTAINER_DESKTOP ignoring!"); 780 continue; 781 } 782 783 widgetInfo.id = c.getLong(idIndex); 784 widgetInfo.screen = c.getInt(screenIndex); 785 widgetInfo.container = container; 786 widgetInfo.cellX = c.getInt(cellXIndex); 787 widgetInfo.cellY = c.getInt(cellYIndex); 788 789 mItems.add(widgetInfo); 790 break; 791 792 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 793 // Read all Launcher-specific widget details 794 int appWidgetId = c.getInt(appWidgetIdIndex); 795 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); 796 appWidgetInfo.id = c.getLong(idIndex); 797 appWidgetInfo.screen = c.getInt(screenIndex); 798 appWidgetInfo.cellX = c.getInt(cellXIndex); 799 appWidgetInfo.cellY = c.getInt(cellYIndex); 800 appWidgetInfo.spanX = c.getInt(spanXIndex); 801 appWidgetInfo.spanY = c.getInt(spanYIndex); 802 803 container = c.getInt(containerIndex); 804 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 805 Log.e(TAG, "Widget found where container " 806 + "!= CONTAINER_DESKTOP -- ignoring!"); 807 continue; 808 } 809 appWidgetInfo.container = c.getInt(containerIndex); 810 811 mAppWidgets.add(appWidgetInfo); 812 break; 813 } 814 } catch (Exception e) { 815 Log.w(TAG, "Desktop items loading interrupted:", e); 816 } 817 } 818 } finally { 819 c.close(); 820 } 821 if (DEBUG_LOADERS) { 822 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 823 } 824 } 825 826 /** 827 * Read everything out of our database. 828 */ 829 private void bindWorkspace() { 830 final long t = SystemClock.uptimeMillis(); 831 832 // Don't use these two variables in any of the callback runnables. 833 // Otherwise we hold a reference to them. 834 Callbacks callbacks = mCallbacks.get(); 835 if (callbacks == null) { 836 // This launcher has exited and nobody bothered to tell us. Just bail. 837 Log.w(TAG, "LoaderThread running with no launcher"); 838 return; 839 } 840 841 int N; 842 // Tell the workspace that we're about to start firing items at it 843 mHandler.post(new Runnable() { 844 public void run() { 845 Callbacks callbacks = tryGetCallbacks(); 846 if (callbacks != null) { 847 callbacks.startBinding(); 848 } 849 } 850 }); 851 // Add the items to the workspace. 852 N = mItems.size(); 853 for (int i=0; i<N; i+=ITEMS_CHUNK) { 854 final int start = i; 855 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 856 mHandler.post(new Runnable() { 857 public void run() { 858 Callbacks callbacks = tryGetCallbacks(); 859 if (callbacks != null) { 860 callbacks.bindItems(mItems, start, start+chunkSize); 861 } 862 } 863 }); 864 } 865 mHandler.post(new Runnable() { 866 public void run() { 867 Callbacks callbacks = tryGetCallbacks(); 868 if (callbacks != null) { 869 callbacks.bindFolders(mFolders); 870 } 871 } 872 }); 873 // Wait until the queue goes empty. 874 mHandler.postIdle(new Runnable() { 875 public void run() { 876 if (DEBUG_LOADERS) { 877 Log.d(TAG, "Going to start binding widgets soon."); 878 } 879 } 880 }); 881 // Bind the widgets, one at a time. 882 // WARNING: this is calling into the workspace from the background thread, 883 // but since getCurrentScreen() just returns the int, we should be okay. This 884 // is just a hint for the order, and if it's wrong, we'll be okay. 885 // TODO: instead, we should have that push the current screen into here. 886 final int currentScreen = callbacks.getCurrentWorkspaceScreen(); 887 N = mAppWidgets.size(); 888 // once for the current screen 889 for (int i=0; i<N; i++) { 890 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 891 if (widget.screen == currentScreen) { 892 mHandler.post(new Runnable() { 893 public void run() { 894 Callbacks callbacks = tryGetCallbacks(); 895 if (callbacks != null) { 896 callbacks.bindAppWidget(widget); 897 } 898 } 899 }); 900 } 901 } 902 // once for the other screens 903 for (int i=0; i<N; i++) { 904 final LauncherAppWidgetInfo widget = mAppWidgets.get(i); 905 if (widget.screen != currentScreen) { 906 mHandler.post(new Runnable() { 907 public void run() { 908 Callbacks callbacks = tryGetCallbacks(); 909 if (callbacks != null) { 910 callbacks.bindAppWidget(widget); 911 } 912 } 913 }); 914 } 915 } 916 // Tell the workspace that we're done. 917 mHandler.post(new Runnable() { 918 public void run() { 919 Callbacks callbacks = tryGetCallbacks(); 920 if (callbacks != null) { 921 callbacks.finishBindingItems(); 922 } 923 } 924 }); 925 // If we're profiling, this is the last thing in the queue. 926 mHandler.post(new Runnable() { 927 public void run() { 928 if (DEBUG_LOADERS) { 929 Log.d(TAG, "bound workspace in " 930 + (SystemClock.uptimeMillis()-t) + "ms"); 931 } 932 if (Launcher.PROFILE_ROTATE) { 933 android.os.Debug.stopMethodTracing(); 934 } 935 } 936 }); 937 } 938 939 private void loadAllApps() { 940 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 941 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 942 943 final Callbacks callbacks = tryGetCallbacks(); 944 if (callbacks == null) { 945 return; 946 } 947 948 final Context context = mContext; 949 final PackageManager packageManager = context.getPackageManager(); 950 951 final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); 952 953 synchronized (mLock) { 954 mBeforeFirstLoad = false; 955 956 mAllAppsList.clear(); 957 if (apps != null) { 958 long t = SystemClock.uptimeMillis(); 959 960 int N = apps.size(); 961 Utilities.BubbleText bubble = new Utilities.BubbleText(context); 962 for (int i=0; i<N && !mStopped; i++) { 963 // This builds the icon bitmaps. 964 mAllAppsList.add(AppInfoCache.cache(apps.get(i), context, bubble)); 965 } 966 Collections.sort(mAllAppsList.data, APP_NAME_COMPARATOR); 967 Collections.sort(mAllAppsList.added, APP_NAME_COMPARATOR); 968 if (DEBUG_LOADERS) { 969 Log.d(TAG, "cached app icons in " 970 + (SystemClock.uptimeMillis()-t) + "ms"); 971 } 972 } 973 } 974 } 975 976 private void bindAllApps() { 977 synchronized (mLock) { 978 final ArrayList<ApplicationInfo> results 979 = (ArrayList<ApplicationInfo>)mAllAppsList.data.clone(); 980 // We're adding this now, so clear out this so we don't re-send them. 981 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 982 mHandler.post(new Runnable() { 983 public void run() { 984 final long t = SystemClock.uptimeMillis(); 985 final int count = results.size(); 986 987 Callbacks callbacks = tryGetCallbacks(); 988 if (callbacks != null) { 989 callbacks.bindAllApplications(results); 990 } 991 992 if (DEBUG_LOADERS) { 993 Log.d(TAG, "bound app " + count + " icons in " 994 + (SystemClock.uptimeMillis()-t) + "ms"); 995 } 996 } 997 }); 998 } 999 } 1000 1001 public void dumpState() { 1002 Log.d(TAG, "mLoader.mLoaderThread.mContext=" + mContext); 1003 Log.d(TAG, "mLoader.mLoaderThread.mWaitThread=" + mWaitThread); 1004 Log.d(TAG, "mLoader.mLoaderThread.mIsLaunching=" + mIsLaunching); 1005 Log.d(TAG, "mLoader.mLoaderThread.mStopped=" + mStopped); 1006 Log.d(TAG, "mLoader.mLoaderThread.mWorkspaceDoneBinding=" + mWorkspaceDoneBinding); 1007 } 1008 } 1009 1010 public void dumpState() { 1011 Log.d(TAG, "mLoader.mLastWorkspaceSeq=" + mLoader.mLastWorkspaceSeq); 1012 Log.d(TAG, "mLoader.mWorkspaceSeq=" + mLoader.mWorkspaceSeq); 1013 Log.d(TAG, "mLoader.mLastAllAppsSeq=" + mLoader.mLastAllAppsSeq); 1014 Log.d(TAG, "mLoader.mAllAppsSeq=" + mLoader.mAllAppsSeq); 1015 Log.d(TAG, "mLoader.mItems size=" + mLoader.mItems.size()); 1016 if (mLoaderThread != null) { 1017 mLoaderThread.dumpState(); 1018 } else { 1019 Log.d(TAG, "mLoader.mLoaderThread=null"); 1020 } 1021 } 1022 } 1023 1024 /** 1025 * Make an ApplicationInfo object for an application. 1026 */ 1027 private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent, 1028 Context context) { 1029 final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0); 1030 1031 if (resolveInfo == null) { 1032 return null; 1033 } 1034 1035 final ApplicationInfo info = new ApplicationInfo(); 1036 final ActivityInfo activityInfo = resolveInfo.activityInfo; 1037 info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context); 1038 if (info.title == null || info.title.length() == 0) { 1039 info.title = activityInfo.loadLabel(manager); 1040 } 1041 if (info.title == null) { 1042 info.title = ""; 1043 } 1044 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 1045 return info; 1046 } 1047 1048 /** 1049 * Make an ApplicationInfo object for a sortcut 1050 */ 1051 private static ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context, 1052 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) { 1053 1054 final ApplicationInfo info = new ApplicationInfo(); 1055 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 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 info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context); 1067 } catch (Exception e) { 1068 info.icon = packageManager.getDefaultActivityIcon(); 1069 } 1070 info.iconResource = new Intent.ShortcutIconResource(); 1071 info.iconResource.packageName = packageName; 1072 info.iconResource.resourceName = resourceName; 1073 info.customIcon = false; 1074 break; 1075 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 1076 byte[] data = c.getBlob(iconIndex); 1077 try { 1078 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 1079 info.icon = new FastBitmapDrawable( 1080 Utilities.createBitmapThumbnail(bitmap, context)); 1081 } catch (Exception e) { 1082 packageManager = context.getPackageManager(); 1083 info.icon = packageManager.getDefaultActivityIcon(); 1084 } 1085 info.filtered = true; 1086 info.customIcon = true; 1087 break; 1088 default: 1089 info.icon = context.getPackageManager().getDefaultActivityIcon(); 1090 info.customIcon = false; 1091 break; 1092 } 1093 return info; 1094 } 1095 1096 private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, 1097 int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { 1098 1099 int iconType = c.getInt(iconTypeIndex); 1100 switch (iconType) { 1101 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 1102 String packageName = c.getString(iconPackageIndex); 1103 String resourceName = c.getString(iconResourceIndex); 1104 PackageManager packageManager = context.getPackageManager(); 1105 try { 1106 Resources resources = packageManager.getResourcesForApplication(packageName); 1107 final int id = resources.getIdentifier(resourceName, null, null); 1108 liveFolderInfo.icon = resources.getDrawable(id); 1109 } catch (Exception e) { 1110 liveFolderInfo.icon = 1111 context.getResources().getDrawable(R.drawable.ic_launcher_folder); 1112 } 1113 liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); 1114 liveFolderInfo.iconResource.packageName = packageName; 1115 liveFolderInfo.iconResource.resourceName = resourceName; 1116 break; 1117 default: 1118 liveFolderInfo.icon = 1119 context.getResources().getDrawable(R.drawable.ic_launcher_folder); 1120 } 1121 } 1122 1123 /** 1124 * Return an existing UserFolderInfo object if we have encountered this ID previously, 1125 * or make a new one. 1126 */ 1127 private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) { 1128 // See if a placeholder was created for us already 1129 FolderInfo folderInfo = folders.get(id); 1130 if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) { 1131 // No placeholder -- create a new instance 1132 folderInfo = new UserFolderInfo(); 1133 folders.put(id, folderInfo); 1134 } 1135 return (UserFolderInfo) folderInfo; 1136 } 1137 1138 /** 1139 * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a 1140 * new one. 1141 */ 1142 private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) { 1143 // See if a placeholder was created for us already 1144 FolderInfo folderInfo = folders.get(id); 1145 if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) { 1146 // No placeholder -- create a new instance 1147 folderInfo = new LiveFolderInfo(); 1148 folders.put(id, folderInfo); 1149 } 1150 return (LiveFolderInfo) folderInfo; 1151 } 1152 1153 private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { 1154 final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI, 1155 new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE, 1156 LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE }, 1157 null, null, null); 1158 1159 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1160 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); 1161 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 1162 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 1163 1164 // boolean changed = false; 1165 1166 try { 1167 while (c.moveToNext()) { 1168 try { 1169 if (c.getInt(itemTypeIndex) != 1170 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1171 continue; 1172 } 1173 1174 final String intentUri = c.getString(intentIndex); 1175 if (intentUri != null) { 1176 final Intent shortcut = Intent.parseUri(intentUri, 0); 1177 if (Intent.ACTION_MAIN.equals(shortcut.getAction())) { 1178 final ComponentName name = shortcut.getComponent(); 1179 if (name != null) { 1180 final ActivityInfo activityInfo = manager.getActivityInfo(name, 0); 1181 final String title = c.getString(titleIndex); 1182 String label = getLabel(manager, activityInfo); 1183 1184 if (title == null || !title.equals(label)) { 1185 final ContentValues values = new ContentValues(); 1186 values.put(LauncherSettings.Favorites.TITLE, label); 1187 1188 resolver.update( 1189 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, 1190 values, "_id=?", 1191 new String[] { String.valueOf(c.getLong(idIndex)) }); 1192 1193 // changed = true; 1194 } 1195 } 1196 } 1197 } 1198 } catch (URISyntaxException e) { 1199 // Ignore 1200 } catch (PackageManager.NameNotFoundException e) { 1201 // Ignore 1202 } 1203 } 1204 } finally { 1205 c.close(); 1206 } 1207 1208 // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null); 1209 } 1210 1211 private static String getLabel(PackageManager manager, ActivityInfo activityInfo) { 1212 String label = activityInfo.loadLabel(manager).toString(); 1213 if (label == null) { 1214 label = manager.getApplicationLabel(activityInfo.applicationInfo).toString(); 1215 if (label == null) { 1216 label = activityInfo.name; 1217 } 1218 } 1219 return label; 1220 } 1221 1222 private static final Collator sCollator = Collator.getInstance(); 1223 public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR 1224 = new Comparator<ApplicationInfo>() { 1225 public final int compare(ApplicationInfo a, ApplicationInfo b) { 1226 return sCollator.compare(a.title.toString(), b.title.toString()); 1227 } 1228 }; 1229 1230 public void dumpState() { 1231 Log.d(TAG, "mBeforeFirstLoad=" + mBeforeFirstLoad); 1232 Log.d(TAG, "mCallbacks=" + mCallbacks); 1233 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); 1234 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); 1235 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); 1236 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); 1237 mLoader.dumpState(); 1238 } 1239} 1240