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