From d7d37b134b7a3b12858ff099069eba369a6b5373 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Wed, 18 Jul 2012 16:15:08 -0700 Subject: DO NOT MERGE Adding sycnhronous loading of current page to support seamless rotation. (Bug 6792288) Change-Id: I341160e8a604cde63443e331f762cebc1ed5ec0c --- src/com/android/launcher2/Launcher.java | 9 +- src/com/android/launcher2/LauncherModel.java | 1110 ++++++++++++++++---------- src/com/android/launcher2/Workspace.java | 8 + 3 files changed, 685 insertions(+), 442 deletions(-) (limited to 'src') diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index f4180cd00..98d4c094d 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -338,6 +338,11 @@ public final class Launcher extends Activity mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); mAppWidgetHost.startListening(); + // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, + // this also ensures that any synchronous binding below doesn't re-trigger another + // LauncherModel load. + mPaused = false; + if (PROFILE_STARTUP) { android.os.Debug.startMethodTracing( Environment.getExternalStorageDirectory() + "/launcher"); @@ -365,7 +370,7 @@ public final class Launcher extends Activity } if (!mRestoring) { - mModel.startLoader(true); + mModel.startLoader(true, mWorkspace.getCurrentPage()); } if (!mModel.isAllAppsLoaded()) { @@ -677,7 +682,7 @@ public final class Launcher extends Activity mPaused = false; if (mRestoring || mOnResumeNeedsLoad) { mWorkspaceLoading = true; - mModel.startLoader(true); + mModel.startLoader(true, -1); mRestoring = false; mOnResumeNeedsLoad = false; } diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java index fc1a26d4b..29723d447 100644 --- a/src/com/android/launcher2/LauncherModel.java +++ b/src/com/android/launcher2/LauncherModel.java @@ -57,7 +57,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one @@ -77,6 +80,7 @@ public class LauncherModel extends BroadcastReceiver { private final Object mLock = new Object(); private DeferredHandler mHandler = new DeferredHandler(); private LoaderTask mLoaderTask; + private boolean mIsLoaderTaskRunning; private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); static { @@ -90,29 +94,41 @@ public class LauncherModel extends BroadcastReceiver { private boolean mWorkspaceLoaded; private boolean mAllAppsLoaded; + // When we are loading pages synchronously, we can't just post the binding of items on the side + // pages as this delays the rotation process. Instead, we wait for a callback from the first + // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start + // a normal load, we also clear this set of Runnables. + static final ArrayList mDeferredBindRunnables = new ArrayList(); + private WeakReference mCallbacks; // < only access in worker thread > private AllAppsList mAllAppsList; - // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by + // The lock that must be acquired before referencing any static bg data structures. Unlike + // other locks, this one can generally be held long-term because we never expect any of these + // static data structures to be referenced outside of the worker thread except on the first + // load after configuration change. + static final Object sBgLock = new Object(); + + // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by // LauncherModel to their ids - static final HashMap sItemsIdMap = new HashMap(); + static final HashMap sBgItemsIdMap = new HashMap(); - // sItems is passed to bindItems, which expects a list of all folders and shortcuts created by + // sBgItems is passed to bindItems, which expects a list of all folders and shortcuts created by // LauncherModel that are directly on the home screen (however, no widgets or shortcuts // within folders). - static final ArrayList sWorkspaceItems = new ArrayList(); + static final ArrayList sBgWorkspaceItems = new ArrayList(); - // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() - static final ArrayList sAppWidgets = + // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() + static final ArrayList sBgAppWidgets = new ArrayList(); - // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final HashMap sFolders = new HashMap(); + // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() + static final HashMap sBgFolders = new HashMap(); - // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database - static final HashMap sDbIconCache = new HashMap(); + // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database + static final HashMap sBgDbIconCache = new HashMap(); // @@ -158,6 +174,28 @@ public class LauncherModel extends BroadcastReceiver { mPreviousConfigMcc = config.mcc; } + /** Runs the specified runnable immediately if called from the main thread, otherwise it is + * posted on the main thread handler. */ + private void runOnMainThread(Runnable r) { + if (sWorkerThread.getThreadId() == Process.myTid()) { + // If we are on the worker thread, post onto the main handler + mHandler.post(r); + } else { + r.run(); + } + } + + /** Runs the specified runnable immediately if called from the worker thread, otherwise it is + * posted on the worker thread handler. */ + private static void runOnWorkerThread(Runnable r) { + if (sWorkerThread.getThreadId() == Process.myTid()) { + r.run(); + } else { + // If we are not on the worker thread, then post to the worker handler + sWorker.post(r); + } + } + public Bitmap getFallbackIcon() { return Bitmap.createBitmap(mDefaultIcon); } @@ -171,26 +209,28 @@ public class LauncherModel extends BroadcastReceiver { }); } - /** Unbinds all the sWorkspaceItems on the main thread, and return a copy of sWorkspaceItems - * that is save to reference from the main thread. */ - private ArrayList unbindWorkspaceItemsOnMainThread() { + /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */ + private void unbindWorkspaceItemsOnMainThread() { // Ensure that we don't use the same workspace items data structure on the main thread // by making a copy of workspace items first. - final ArrayList workspaceItems = new ArrayList(sWorkspaceItems); - final ArrayList appWidgets = new ArrayList(sAppWidgets); - mHandler.post(new Runnable() { - @Override - public void run() { - for (ItemInfo item : workspaceItems) { - item.unbind(); - } - for (ItemInfo item : appWidgets) { - item.unbind(); - } - } - }); - - return workspaceItems; + final ArrayList tmpWorkspaceItems = new ArrayList(); + final ArrayList tmpAppWidgets = new ArrayList(); + synchronized (sBgLock) { + tmpWorkspaceItems.addAll(sBgWorkspaceItems); + tmpAppWidgets.addAll(sBgAppWidgets); + } + Runnable r = new Runnable() { + @Override + public void run() { + for (ItemInfo item : tmpWorkspaceItems) { + item.unbind(); + } + for (ItemInfo item : tmpAppWidgets) { + item.unbind(); + } + } + }; + runOnMainThread(r); } /** @@ -218,35 +258,35 @@ public class LauncherModel extends BroadcastReceiver { public void run() { cr.update(uri, values, null, null); - ItemInfo modelItem = sItemsIdMap.get(itemId); - if (item != modelItem) { - // the modelItem needs to match up perfectly with item if our model is to be - // consistent with the database-- for now, just require modelItem == item - String msg = "item: " + ((item != null) ? item.toString() : "null") + - "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") + - "Error: ItemInfo passed to " + callingFunction + " doesn't match original"; - throw new RuntimeException(msg); - } + // Lock on mBgLock *after* the db operation + synchronized (sBgLock) { + ItemInfo modelItem = sBgItemsIdMap.get(itemId); + if (item != modelItem) { + // the modelItem needs to match up perfectly with item if our model is to be + // consistent with the database-- for now, just require modelItem == item + String msg = "item: " + ((item != null) ? item.toString() : "null") + + "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") + + "Error: ItemInfo passed to " + callingFunction + " doesn't match " + + "original"; + throw new RuntimeException(msg); + } + + // Items are added/removed from the corresponding FolderInfo elsewhere, such + // as in Workspace.onDrop. Here, we just add/remove them from the list of items + // that are on the desktop, as appropriate + if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (!sBgWorkspaceItems.contains(modelItem)) { + sBgWorkspaceItems.add(modelItem); - // Items are added/removed from the corresponding FolderInfo elsewhere, such - // as in Workspace.onDrop. Here, we just add/remove them from the list of items - // that are on the desktop, as appropriate - if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - if (!sWorkspaceItems.contains(modelItem)) { - sWorkspaceItems.add(modelItem); + } + } else { + sBgWorkspaceItems.remove(modelItem); } - } else { - sWorkspaceItems.remove(modelItem); } } }; - - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } + runOnWorkerThread(r); } /** @@ -450,36 +490,33 @@ public class LauncherModel extends BroadcastReceiver { public void run() { cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); - - if (sItemsIdMap.containsKey(item.id)) { - // we should not be adding new items in the db with the same id - throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " + - "addItemToDatabase already exists." + item.toString()); - } - sItemsIdMap.put(item.id, item); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sFolders.put(item.id, (FolderInfo) item); - // Fall through - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - sWorkspaceItems.add(item); - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sAppWidgets.add((LauncherAppWidgetInfo) item); - break; + // Lock on mBgLock *after* the db operation + synchronized (sBgLock) { + if (sBgItemsIdMap.containsKey(item.id)) { + // we should not be adding new items in the db with the same id + throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " + + "addItemToDatabase already exists." + item.toString()); + } + sBgItemsIdMap.put(item.id, item); + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + sBgFolders.put(item.id, (FolderInfo) item); + // Fall through + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + sBgWorkspaceItems.add(item); + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + sBgAppWidgets.add((LauncherAppWidgetInfo) item); + break; + } } } }; - - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } + runOnWorkerThread(r); } /** @@ -519,28 +556,27 @@ public class LauncherModel extends BroadcastReceiver { Runnable r = new Runnable() { public void run() { cr.delete(uriToDelete, null, null); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sFolders.remove(item.id); - sWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - sWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sAppWidgets.remove((LauncherAppWidgetInfo) item); - break; + // Lock on mBgLock *after* the db operation + synchronized (sBgLock) { + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + sBgFolders.remove(item.id); + sBgWorkspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + sBgWorkspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + sBgAppWidgets.remove((LauncherAppWidgetInfo) item); + break; + } + sBgItemsIdMap.remove(item.id); + sBgDbIconCache.remove(item); } - sItemsIdMap.remove(item.id); - sDbIconCache.remove(item); } }; - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } + runOnWorkerThread(r); } /** @@ -552,24 +588,26 @@ public class LauncherModel extends BroadcastReceiver { Runnable r = new Runnable() { public void run() { cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); - sItemsIdMap.remove(info.id); - sFolders.remove(info.id); - sDbIconCache.remove(info); - sWorkspaceItems.remove(info); + // Lock on mBgLock *after* the db operation + synchronized (sBgLock) { + sBgItemsIdMap.remove(info.id); + sBgFolders.remove(info.id); + sBgDbIconCache.remove(info); + sBgWorkspaceItems.remove(info); + } cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); - for (ItemInfo childInfo : info.contents) { - sItemsIdMap.remove(childInfo.id); - sDbIconCache.remove(childInfo); + // Lock on mBgLock *after* the db operation + synchronized (sBgLock) { + for (ItemInfo childInfo : info.contents) { + sBgItemsIdMap.remove(childInfo.id); + sBgDbIconCache.remove(childInfo); + } } } }; - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } + runOnWorkerThread(r); } /** @@ -697,7 +735,7 @@ public class LauncherModel extends BroadcastReceiver { } } if (runLoader) { - startLoader(false); + startLoader(false, -1); } } @@ -715,24 +753,42 @@ public class LauncherModel extends BroadcastReceiver { return isLaunching; } - public void startLoader(boolean isLaunching) { + public void startLoader(boolean isLaunching, int synchronousBindPage) { synchronized (mLock) { if (DEBUG_LOADERS) { Log.d(TAG, "startLoader isLaunching=" + isLaunching); } + // Clear any deferred bind-runnables from the synchronized load process + // We must do this before any loading/binding is scheduled below. + mDeferredBindRunnables.clear(); + // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { // If there is already one running, tell it to stop. // also, don't downgrade isLaunching if we're already running isLaunching = isLaunching || stopLoaderLocked(); mLoaderTask = new LoaderTask(mApp, isLaunching); - sWorkerThread.setPriority(Thread.NORM_PRIORITY); - sWorker.post(mLoaderTask); + if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) { + mLoaderTask.runBindSynchronousPage(synchronousBindPage); + } else { + sWorkerThread.setPriority(Thread.NORM_PRIORITY); + sWorker.post(mLoaderTask); + } } } } + void bindRemainingSynchronousPages() { + // Post the remaining side pages to be loaded + if (!mDeferredBindRunnables.isEmpty()) { + for (final Runnable r : mDeferredBindRunnables) { + mHandler.post(r); + } + mDeferredBindRunnables.clear(); + } + } + public void stopLoader() { synchronized (mLock) { if (mLoaderTask != null) { @@ -762,11 +818,11 @@ public class LauncherModel extends BroadcastReceiver { */ private class LoaderTask implements Runnable { private Context mContext; - private Thread mWaitThread; private boolean mIsLaunching; private boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; private boolean mLoadAndBindStepFinished; + private HashMap mLabelCache; LoaderTask(Context context, boolean isLaunching) { @@ -802,7 +858,7 @@ public class LauncherModel extends BroadcastReceiver { } // Bind the workspace - bindWorkspace(); + bindWorkspace(-1); } private void waitForIdle() { @@ -839,7 +895,41 @@ public class LauncherModel extends BroadcastReceiver { } } + void runBindSynchronousPage(int synchronousBindPage) { + if (synchronousBindPage < 0) { + // Ensure that we have a valid page index to load synchronously + throw new RuntimeException("Should not call runBindSynchronousPage() without " + + "valid page index"); + } + if (!mAllAppsLoaded || !mWorkspaceLoaded) { + // Ensure that we don't try and bind a specified page when the pages have not been + // loaded already (we should load everything asynchronously in that case) + throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); + } + synchronized (mLock) { + if (mIsLoaderTaskRunning) { + // Ensure that we are never running the background loading at this point since + // we also touch the background collections + throw new RuntimeException("Error! Background loading is already running"); + } + } + + // XXX: Throw an exception if we are already loading (since we touch the worker thread + // data structures, we can't allow any other thread to touch that data, but because + // this call is synchronous, we can get away with not locking). + + // Divide the set of loaded items into those that we are binding synchronously, and + // everything else that is to be bound normally (asynchronously). + bindWorkspace(synchronousBindPage); + // XXX: For now, continue posting the binding of AllApps as there are other issues that + // arise from that. + onlyBindAllApps(); + } + public void run() { + synchronized (mLock) { + mIsLoaderTaskRunning = true; + } // Optimize for end-user experience: if the Launcher is up and // running with the // All Apps interface in the foreground, load All Apps first. Otherwise, load the // workspace first (default). @@ -895,10 +985,12 @@ public class LauncherModel extends BroadcastReceiver { // Update the saved icons if necessary if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); - for (Object key : sDbIconCache.keySet()) { - updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key)); + synchronized (sBgLock) { + for (Object key : sBgDbIconCache.keySet()) { + updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); + } + sBgDbIconCache.clear(); } - sDbIconCache.clear(); // Clear out this reference, otherwise we end up holding it until all of the // callback runnables are done. @@ -909,6 +1001,7 @@ public class LauncherModel extends BroadcastReceiver { if (mLoaderTask == this) { mLoaderTask = null; } + mIsLoaderTaskRunning = false; } } @@ -1008,278 +1101,365 @@ public class LauncherModel extends BroadcastReceiver { // Make sure the default workspace is loaded, if needed mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(); - sWorkspaceItems.clear(); - sAppWidgets.clear(); - sFolders.clear(); - sItemsIdMap.clear(); - sDbIconCache.clear(); + synchronized (sBgLock) { + sBgWorkspaceItems.clear(); + sBgAppWidgets.clear(); + sBgFolders.clear(); + sBgItemsIdMap.clear(); + sBgDbIconCache.clear(); - final ArrayList itemsToRemove = new ArrayList(); + final ArrayList itemsToRemove = new ArrayList(); - final Cursor c = contentResolver.query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); + final Cursor c = contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); - // +1 for the hotseat (it can be larger than the workspace) - // Load workspace in reverse order to ensure that latest items are loaded first (and - // before any earlier duplicates) - final ItemInfo occupied[][][] = - new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; + // +1 for the hotseat (it can be larger than the workspace) + // Load workspace in reverse order to ensure that latest items are loaded first (and + // before any earlier duplicates) + final ItemInfo occupied[][][] = + new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; - try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - final int intentIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.INTENT); - final int titleIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.TITLE); - final int iconTypeIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_TYPE); - final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); - final int iconPackageIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_PACKAGE); - final int iconResourceIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_RESOURCE); - final int containerIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.CONTAINER); - final int itemTypeIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ITEM_TYPE); - final int appWidgetIdIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.APPWIDGET_ID); - final int screenIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.CELLY); - final int spanXIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.SPANX); - final int spanYIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.SPANY); - //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - //final int displayModeIndex = c.getColumnIndexOrThrow( - // LauncherSettings.Favorites.DISPLAY_MODE); - - ShortcutInfo info; - String intentDescription; - LauncherAppWidgetInfo appWidgetInfo; - int container; - long id; - Intent intent; - - while (!mStopped && c.moveToNext()) { - try { - int itemType = c.getInt(itemTypeIndex); - - switch (itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - intentDescription = c.getString(intentIndex); - try { - intent = Intent.parseUri(intentDescription, 0); - } catch (URISyntaxException e) { - continue; - } - - if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = getShortcutInfo(manager, intent, context, c, iconIndex, - titleIndex, mLabelCache); - } else { - info = getShortcutInfo(c, context, iconTypeIndex, - iconPackageIndex, iconResourceIndex, iconIndex, - titleIndex); + try { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ITEM_TYPE); + final int appWidgetIdIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.APPWIDGET_ID); + final int screenIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.CELLY); + final int spanXIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.SPANY); + //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + //final int displayModeIndex = c.getColumnIndexOrThrow( + // LauncherSettings.Favorites.DISPLAY_MODE); + + ShortcutInfo info; + String intentDescription; + LauncherAppWidgetInfo appWidgetInfo; + int container; + long id; + Intent intent; + + while (!mStopped && c.moveToNext()) { + try { + int itemType = c.getInt(itemTypeIndex); + + switch (itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + intentDescription = c.getString(intentIndex); + try { + intent = Intent.parseUri(intentDescription, 0); + } catch (URISyntaxException e) { + continue; + } - // App shortcuts that used to be automatically added to Launcher - // didn't always have the correct intent flags set, so do that here - if (intent.getAction() != null && + if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + info = getShortcutInfo(manager, intent, context, c, iconIndex, + titleIndex, mLabelCache); + } else { + info = getShortcutInfo(c, context, iconTypeIndex, + iconPackageIndex, iconResourceIndex, iconIndex, + titleIndex); + + // App shortcuts that used to be automatically added to Launcher + // didn't always have the correct intent flags set, so do that + // here + if (intent.getAction() != null && intent.getCategories() != null && intent.getAction().equals(Intent.ACTION_MAIN) && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } } - } - if (info != null) { - info.intent = intent; - info.id = c.getLong(idIndex); + if (info != null) { + info.intent = intent; + info.id = c.getLong(idIndex); + container = c.getInt(containerIndex); + info.container = container; + info.screen = c.getInt(screenIndex); + info.cellX = c.getInt(cellXIndex); + info.cellY = c.getInt(cellYIndex); + + // check & update map of what's occupied + if (!checkItemPlacement(occupied, info)) { + break; + } + + switch (container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: + sBgWorkspaceItems.add(info); + break; + default: + // Item is in a user folder + FolderInfo folderInfo = + findOrMakeFolder(sBgFolders, container); + folderInfo.add(info); + break; + } + sBgItemsIdMap.put(info.id, info); + + // now that we've loaded everthing re-save it with the + // icon in case it disappears somehow. + queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex); + } else { + // Failed to load the shortcut, probably because the + // activity manager couldn't resolve it (maybe the app + // was uninstalled), or the db row was somehow screwed up. + // Delete it. + id = c.getLong(idIndex); + Log.e(TAG, "Error loading shortcut " + id + ", removing it"); + contentResolver.delete(LauncherSettings.Favorites.getContentUri( + id, false), null, null); + } + break; + + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + id = c.getLong(idIndex); + FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); + + folderInfo.title = c.getString(titleIndex); + folderInfo.id = id; container = c.getInt(containerIndex); - info.container = container; - info.screen = c.getInt(screenIndex); - info.cellX = c.getInt(cellXIndex); - info.cellY = c.getInt(cellYIndex); + folderInfo.container = container; + folderInfo.screen = c.getInt(screenIndex); + folderInfo.cellX = c.getInt(cellXIndex); + folderInfo.cellY = c.getInt(cellYIndex); // check & update map of what's occupied - if (!checkItemPlacement(occupied, info)) { + if (!checkItemPlacement(occupied, folderInfo)) { break; } - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sWorkspaceItems.add(info); - break; - default: - // Item is in a user folder - FolderInfo folderInfo = - findOrMakeFolder(sFolders, container); - folderInfo.add(info); - break; + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: + sBgWorkspaceItems.add(folderInfo); + break; } - sItemsIdMap.put(info.id, info); - // now that we've loaded everthing re-save it with the - // icon in case it disappears somehow. - queueIconToBeChecked(sDbIconCache, info, c, iconIndex); - } else { - // Failed to load the shortcut, probably because the - // activity manager couldn't resolve it (maybe the app - // was uninstalled), or the db row was somehow screwed up. - // Delete it. + sBgItemsIdMap.put(folderInfo.id, folderInfo); + sBgFolders.put(folderInfo.id, folderInfo); + break; + + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + // Read all Launcher-specific widget details + int appWidgetId = c.getInt(appWidgetIdIndex); id = c.getLong(idIndex); - Log.e(TAG, "Error loading shortcut " + id + ", removing it"); - contentResolver.delete(LauncherSettings.Favorites.getContentUri( - id, false), null, null); - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - id = c.getLong(idIndex); - FolderInfo folderInfo = findOrMakeFolder(sFolders, id); - - folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; - container = c.getInt(containerIndex); - folderInfo.container = container; - folderInfo.screen = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); - - // check & update map of what's occupied - if (!checkItemPlacement(occupied, folderInfo)) { + final AppWidgetProviderInfo provider = + widgets.getAppWidgetInfo(appWidgetId); + + if (!isSafeMode && (provider == null || provider.provider == null || + provider.provider.getPackageName() == null)) { + String log = "Deleting widget that isn't installed anymore: id=" + + id + " appWidgetId=" + appWidgetId; + Log.e(TAG, log); + Launcher.sDumpLogs.add(log); + itemsToRemove.add(id); + } else { + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + provider.provider); + appWidgetInfo.id = id; + appWidgetInfo.screen = c.getInt(screenIndex); + appWidgetInfo.cellX = c.getInt(cellXIndex); + appWidgetInfo.cellY = c.getInt(cellYIndex); + appWidgetInfo.spanX = c.getInt(spanXIndex); + appWidgetInfo.spanY = c.getInt(spanYIndex); + int[] minSpan = Launcher.getMinSpanForWidget(context, provider); + appWidgetInfo.minSpanX = minSpan[0]; + appWidgetInfo.minSpanY = minSpan[1]; + + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && + container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + Log.e(TAG, "Widget found where container != " + + "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); + continue; + } + appWidgetInfo.container = c.getInt(containerIndex); + + // check & update map of what's occupied + if (!checkItemPlacement(occupied, appWidgetInfo)) { + break; + } + sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); + sBgAppWidgets.add(appWidgetInfo); + } break; } - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sWorkspaceItems.add(folderInfo); - break; - } - - sItemsIdMap.put(folderInfo.id, folderInfo); - sFolders.put(folderInfo.id, folderInfo); - break; - - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - // Read all Launcher-specific widget details - int appWidgetId = c.getInt(appWidgetIdIndex); - id = c.getLong(idIndex); - - final AppWidgetProviderInfo provider = - widgets.getAppWidgetInfo(appWidgetId); - - if (!isSafeMode && (provider == null || provider.provider == null || - provider.provider.getPackageName() == null)) { - String log = "Deleting widget that isn't installed anymore: id=" - + id + " appWidgetId=" + appWidgetId; - Log.e(TAG, log); - Launcher.sDumpLogs.add(log); - itemsToRemove.add(id); - } else { - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - provider.provider); - appWidgetInfo.id = id; - appWidgetInfo.screen = c.getInt(screenIndex); - appWidgetInfo.cellX = c.getInt(cellXIndex); - appWidgetInfo.cellY = c.getInt(cellYIndex); - appWidgetInfo.spanX = c.getInt(spanXIndex); - appWidgetInfo.spanY = c.getInt(spanYIndex); - int[] minSpan = Launcher.getMinSpanForWidget(context, provider); - appWidgetInfo.minSpanX = minSpan[0]; - appWidgetInfo.minSpanY = minSpan[1]; + } catch (Exception e) { + Log.w(TAG, "Desktop items loading interrupted:", e); + } + } + } finally { + c.close(); + } - container = c.getInt(containerIndex); - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && - container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - Log.e(TAG, "Widget found where container " - + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); - continue; - } - appWidgetInfo.container = c.getInt(containerIndex); + if (itemsToRemove.size() > 0) { + ContentProviderClient client = contentResolver.acquireContentProviderClient( + LauncherSettings.Favorites.CONTENT_URI); + // Remove dead items + for (long id : itemsToRemove) { + if (DEBUG_LOADERS) { + Log.d(TAG, "Removed id = " + id); + } + // Don't notify content observers + try { + client.delete(LauncherSettings.Favorites.getContentUri(id, false), + null, null); + } catch (RemoteException e) { + Log.w(TAG, "Could not remove id = " + id); + } + } + } - // check & update map of what's occupied - if (!checkItemPlacement(occupied, appWidgetInfo)) { - break; - } - sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); - sAppWidgets.add(appWidgetInfo); + if (DEBUG_LOADERS) { + Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); + Log.d(TAG, "workspace layout: "); + for (int y = 0; y < mCellCountY; y++) { + String line = ""; + for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { + if (s > 0) { + line += " | "; + } + for (int x = 0; x < mCellCountX; x++) { + line += ((occupied[s][x][y] != null) ? "#" : "."); } - break; } - } catch (Exception e) { - Log.w(TAG, "Desktop items loading interrupted:", e); + Log.d(TAG, "[ " + line + " ]"); } } - } finally { - c.close(); } + } - if (itemsToRemove.size() > 0) { - ContentProviderClient client = contentResolver.acquireContentProviderClient( - LauncherSettings.Favorites.CONTENT_URI); - // Remove dead items - for (long id : itemsToRemove) { - if (DEBUG_LOADERS) { - Log.d(TAG, "Removed id = " + id); + /** Filters the set of items who are directly or indirectly (via another container) on the + * specified screen. */ + private void filterCurrentWorkspaceItems(int currentScreen, + ArrayList allWorkspaceItems, + ArrayList currentScreenItems, + ArrayList otherScreenItems) { + // Purge any null ItemInfos + Iterator iter = allWorkspaceItems.iterator(); + while (iter.hasNext()) { + ItemInfo i = iter.next(); + if (i == null) { + iter.remove(); + } + } + + // If we aren't filtering on a screen, then the set of items to load is the full set of + // items given. + if (currentScreen < 0) { + currentScreenItems.addAll(allWorkspaceItems); + } + + // Order the set of items by their containers first, this allows use to walk through the + // list sequentially, build up a list of containers that are in the specified screen, + // as well as all items in those containers. + Set itemsOnScreen = new HashSet(); + Collections.sort(allWorkspaceItems, new Comparator() { + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + return (int) (lhs.container - rhs.container); + } + }); + for (ItemInfo info : allWorkspaceItems) { + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (info.screen == currentScreen) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); } - // Don't notify content observers - try { - client.delete(LauncherSettings.Favorites.getContentUri(id, false), - null, null); - } catch (RemoteException e) { - Log.w(TAG, "Could not remove id = " + id); + } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + if (itemsOnScreen.contains(info.container)) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); } } } + } + + /** Filters the set of widgets which are on the specified screen. */ + private void filterCurrentAppWidgets(int currentScreen, + ArrayList appWidgets, + ArrayList currentScreenWidgets, + ArrayList otherScreenWidgets) { + // If we aren't filtering on a screen, then the set of items to load is the full set of + // widgets given. + if (currentScreen < 0) { + currentScreenWidgets.addAll(appWidgets); + } - if (DEBUG_LOADERS) { - Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); - Log.d(TAG, "workspace layout: "); - for (int y = 0; y < mCellCountY; y++) { - String line = ""; - for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { - if (s > 0) { - line += " | "; - } - for (int x = 0; x < mCellCountX; x++) { - line += ((occupied[s][x][y] != null) ? "#" : "."); - } - } - Log.d(TAG, "[ " + line + " ]"); + for (LauncherAppWidgetInfo widget : appWidgets) { + if (widget == null) continue; + if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + widget.screen == currentScreen) { + currentScreenWidgets.add(widget); + } else { + otherScreenWidgets.add(widget); } } } - /** - * Read everything out of our database. - */ - private void bindWorkspace() { - final long t = SystemClock.uptimeMillis(); + /** Filters the set of folders which are on the specified screen. */ + private void filterCurrentFolders(int currentScreen, + HashMap itemsIdMap, + HashMap folders, + HashMap currentScreenFolders, + HashMap otherScreenFolders) { + // If we aren't filtering on a screen, then the set of items to load is the full set of + // widgets given. + if (currentScreen < 0) { + currentScreenFolders.putAll(folders); + } - // Don't use these two variables in any of the callback runnables. - // Otherwise we hold a reference to them. - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher"); - return; + for (long id : folders.keySet()) { + ItemInfo info = itemsIdMap.get(id); + FolderInfo folder = folders.get(id); + if (info == null || folder == null) continue; + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + info.screen == currentScreen) { + currentScreenFolders.put(id, folder); + } else { + otherScreenFolders.put(id, folder); + } } + } - // Get the list of workspace items to load and unbind the existing ShortcutInfos - // before we call startBinding() below. - final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); - final ArrayList tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread(); - // Order the items for loading as follows: current workspace, hotseat, everything else - Collections.sort(tmpWorkspaceItems, new Comparator() { + /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to + * right) */ + private void sortWorkspaceItemsSpatially(ArrayList workspaceItems) { + // XXX: review this + Collections.sort(workspaceItems, new Comparator() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { int cellCountX = LauncherModel.getCellCountX(); @@ -1293,108 +1473,155 @@ public class LauncherModel extends BroadcastReceiver { return (int) (lr - rr); } }); - // Precondition: the items are ordered by page, screen - final ArrayList workspaceItems = new ArrayList(); - for (ItemInfo ii : tmpWorkspaceItems) { - // Prepend the current items, hotseat items, append everything else - if (ii.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - ii.screen == currentScreen) { - workspaceItems.add(0, ii); - } else if (ii.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - workspaceItems.add(0, ii); - } else { - workspaceItems.add(ii); - } - } + } - // Tell the workspace that we're about to start firing items at it - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.startBinding(); - } - } - }); + private void bindWorkspaceItems(final Callbacks oldCallbacks, + final ArrayList workspaceItems, + final ArrayList appWidgets, + final HashMap folders, + ArrayList deferredBindRunnables) { + + final boolean postOnMainThread = (deferredBindRunnables != null); - // Add the items to the workspace. + // Bind the workspace items int N = workspaceItems.size(); - for (int i=0; i folders = new HashMap(sFolders); - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindFolders(folders); - } - } - }); - // Wait until the queue goes empty. - mHandler.post(new Runnable() { - public void run() { - if (DEBUG_LOADERS) { - Log.d(TAG, "Going to start binding widgets soon."); - } + }; + if (postOnMainThread) { + deferredBindRunnables.add(r); + } else { + runOnMainThread(r); } - }); - // Bind the widgets, one at a time. - // WARNING: this is calling into the workspace from the background thread, - // but since getCurrentScreen() just returns the int, we should be okay. This - // is just a hint for the order, and if it's wrong, we'll be okay. - // TODO: instead, we should have that push the current screen into here. - N = sAppWidgets.size(); - // once for the current screen - for (int i=0; i -1) ? synchronizeBindPage : + oldCallbacks.getCurrentWorkspaceScreen(); + + // Load all the items that are on the current page first (and in the process, unbind + // all the existing workspace items before we call startBinding() below. + unbindWorkspaceItemsOnMainThread(); + ArrayList workspaceItems = new ArrayList(); + ArrayList appWidgets = + new ArrayList(); + HashMap folders = new HashMap(); + HashMap itemsIdMap = new HashMap(); + synchronized (sBgLock) { + workspaceItems.addAll(sBgWorkspaceItems); + appWidgets.addAll(sBgAppWidgets); + folders.putAll(sBgFolders); + itemsIdMap.putAll(sBgItemsIdMap); + } + + ArrayList currentWorkspaceItems = new ArrayList(); + ArrayList otherWorkspaceItems = new ArrayList(); + ArrayList currentAppWidgets = + new ArrayList(); + ArrayList otherAppWidgets = + new ArrayList(); + HashMap currentFolders = new HashMap(); + HashMap otherFolders = new HashMap(); + + // Separate the items that are on the current screen, and all the other remaining items + filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems, + otherWorkspaceItems); + filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets, + otherAppWidgets); + filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders, + otherFolders); + sortWorkspaceItemsSpatially(currentWorkspaceItems); + sortWorkspaceItemsSpatially(otherWorkspaceItems); + + // Tell the workspace that we're about to start binding items + r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { - callbacks.finishBindingItems(); + callbacks.startBinding(); } } - }); - // Cleanup - mHandler.post(new Runnable() { + }; + runOnMainThread(r); + + // Load items on the current page + bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, + currentFolders, null); + + // Load all the remaining pages + mDeferredBindRunnables.clear(); + bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, + mDeferredBindRunnables); + + // Tell the workspace that we're done binding items + r = new Runnable() { public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.finishBindingItems(); + } + // If we're profiling, ensure this is the last thing in the queue. if (DEBUG_LOADERS) { Log.d(TAG, "bound workspace in " @@ -1403,7 +1630,8 @@ public class LauncherModel extends BroadcastReceiver { mIsLoadingAndBindingWorkspace = false; } - }); + }; + mDeferredBindRunnables.add(r); } private void loadAndBindAllApps() { @@ -1448,7 +1676,6 @@ public class LauncherModel extends BroadcastReceiver { } } }); - } private void loadAllAppsByBatch() { @@ -1566,12 +1793,13 @@ public class LauncherModel extends BroadcastReceiver { } public void dumpState() { - Log.d(TAG, "mLoaderTask.mContext=" + mContext); - Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread); - Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); - Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); - Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); - Log.d(TAG, "mItems size=" + sWorkspaceItems.size()); + synchronized (sBgLock) { + Log.d(TAG, "mLoaderTask.mContext=" + mContext); + Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); + Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); + Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); + Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); + } } } @@ -1702,11 +1930,13 @@ public class LauncherModel extends BroadcastReceiver { */ ArrayList getShortcutInfosForPackage(String packageName) { ArrayList infos = new ArrayList(); - for (ItemInfo i : sWorkspaceItems) { - if (i instanceof ShortcutInfo) { - ShortcutInfo info = (ShortcutInfo) i; - if (packageName.equals(info.getPackageName())) { - infos.add(info); + synchronized (sBgLock) { + for (ItemInfo i : sBgWorkspaceItems) { + if (i instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) i; + if (packageName.equals(info.getPackageName())) { + infos.add(info); + } } } } diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index 2d2340a3e..331e86f33 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -1329,6 +1329,14 @@ public class Workspace extends SmoothPagedView } super.onDraw(canvas); + + // Call back to LauncherModel to finish binding after the first draw + post(new Runnable() { + @Override + public void run() { + mLauncher.getModel().bindRemainingSynchronousPages(); + } + }); } boolean isDrawingBackgroundGradient() { -- cgit v1.2.3