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