diff options
author | Steve Kondik <shade@chemlab.org> | 2012-11-05 12:14:58 -0800 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2012-11-05 12:14:58 -0800 |
commit | f5baac88b85efa794ae7909f784538f379dd36f2 (patch) | |
tree | c55b82edcaae558d71fc12f856bb93d9ae8ce8dc /src/com | |
parent | 5bf25aecb90bd49ca3a8affea877e570b75baed6 (diff) | |
parent | 4124016ae5c8174aa53433894b896604ffa52c67 (diff) | |
download | packages_apps_trebuchet-f5baac88b85efa794ae7909f784538f379dd36f2.zip packages_apps_trebuchet-f5baac88b85efa794ae7909f784538f379dd36f2.tar.gz packages_apps_trebuchet-f5baac88b85efa794ae7909f784538f379dd36f2.tar.bz2 |
Merge branch 'master' of https://android.googlesource.com/platform/packages/apps/Launcher2 into aosp
Conflicts:
res/layout-land/drop_target_bar.xml
src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java
src/com/cyanogenmod/trebuchet/Launcher.java
src/com/cyanogenmod/trebuchet/LauncherModel.java
Change-Id: I5aa702e333da27645b0d765b233f4d8a89991cb9
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java | 24 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java | 30 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java | 19 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/CellLayout.java | 8 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/DeferredHandler.java | 12 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/Launcher.java | 122 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/LauncherModel.java | 1134 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/PagedViewWidget.java | 27 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java | 2 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/Workspace.java | 38 |
10 files changed, 902 insertions, 514 deletions
diff --git a/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java index 8d17bb3..0571541 100644 --- a/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java +++ b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java @@ -54,6 +54,9 @@ public class AppWidgetResizeFrame extends FrameLayout { private int mBackgroundPadding; private int mTouchTargetWidth; + private int mTopTouchRegionAdjustment = 0; + private int mBottomTouchRegionAdjustment = 0; + int[] mDirectionVector = new int[2]; final int SNAP_DURATION = 150; @@ -147,10 +150,12 @@ public class AppWidgetResizeFrame extends FrameLayout { public boolean beginResizeIfPointInRegion(int x, int y) { boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; + mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive; mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive; - mTopBorderActive = (y < mTouchTargetWidth) && verticalActive; - mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive; + mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive; + mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment) + && verticalActive; boolean anyBordersActive = mLeftBorderActive || mRightBorderActive || mTopBorderActive || mBottomBorderActive; @@ -386,13 +391,20 @@ public class AppWidgetResizeFrame extends FrameLayout { int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft; int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop; - // We need to make sure the frame stays within the bounds of the CellLayout + // We need to make sure the frame's touchable regions lie fully within the bounds of the + // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions + // down accordingly to provide a proper touch target. if (newY < 0) { - newHeight -= -newY; - newY = 0; + // In this case we shift the touch region down to start at the top of the DragLayer + mTopTouchRegionAdjustment = -newY; + } else { + mTopTouchRegionAdjustment = 0; } if (newY + newHeight > mDragLayer.getHeight()) { - newHeight -= newY + newHeight - mDragLayer.getHeight(); + // In this case we shift the touch region up to end at the bottom of the DragLayer + mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight()); + } else { + mBottomTouchRegionAdjustment = 0; } if (!animate) { diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java index 86dcce8..f64cb72 100644 --- a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java @@ -616,18 +616,22 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - public void onPackagesUpdated() { - // TODO: this isn't ideal, but we actually need to delay here. This call is triggered - // by a broadcast receiver, and in order for it to work correctly, we need to know that - // the AppWidgetService has already received and processed the same broadcast. Since there - // is no guarantee about ordering of broadcast receipt, we just delay here. This is a - // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget - // packages are added, updated or removed. - postDelayed(new Runnable() { - public void run() { - updatePackages(); - } - }, 1500); + public void onPackagesUpdated(boolean immediate) { + if (immediate) { + updatePackages(); + } else { + // TODO: this isn't ideal, but we actually need to delay here. This call is triggered + // by a broadcast receiver, and in order for it to work correctly, we need to know that + // the AppWidgetService has already received and processed the same broadcast. Since there + // is no guarantee about ordering of broadcast receipt, we just delay here. This is a + // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget + // packages are added, updated or removed. + postDelayed(new Runnable() { + public void run() { + updatePackages(); + } + }, 1500); + } } public void updatePackages() { @@ -666,7 +670,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen @Override public void onClick(View v) { // When we have exited all apps or are in transition, disregard clicks - if (!mLauncher.isAllAppsCustomizeOpen() || + if (!mLauncher.isAllAppsVisible() || mLauncher.getWorkspace().isSwitchingState()) return; if (v instanceof PagedViewIcon) { diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java index b1e75b3..1e1e730 100644 --- a/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java @@ -50,7 +50,6 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona private ViewGroup mTabs; private ViewGroup mTabsContainer; private AppsCustomizePagedView mAppsCustomizePane; - private boolean mSuppressContentCallback = false; private FrameLayout mAnimationBuffer; private LinearLayout mContent; @@ -92,13 +91,15 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona * reflects the new content (but doesn't do the animation and logic associated with changing * tabs manually). */ - private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { + void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { + setOnTabChangedListener(null); onTabChangedStart(); onTabChangedEnd(type); + setCurrentTabByTag(getTabTagForContentType(type)); + setOnTabChangedListener(this); } void selectAppsTab() { setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); - setCurrentTabByTag(APPS_TAB_TAG); } void selectWidgetsTab() { setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); @@ -177,10 +178,11 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { // Set the width and show the tab bar mTabs.getLayoutParams().width = contentWidth; - post(mRelayoutAndMakeVisible); + mRelayoutAndMakeVisible.run(); } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public boolean onInterceptTouchEvent(MotionEvent ev) { @@ -226,10 +228,6 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona @Override public void onTabChanged(String tabId) { final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); - if (mSuppressContentCallback) { - mSuppressContentCallback = false; - return; - } if (!mAppsCustomizePane.isContentType(type) || mJoinWidgetsApps) { @@ -321,8 +319,9 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona } public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { - mSuppressContentCallback = true; + setOnTabChangedListener(null); setCurrentTabByTag(getTabTagForContentType(type)); + setOnTabChangedListener(this); } /** diff --git a/src/com/cyanogenmod/trebuchet/CellLayout.java b/src/com/cyanogenmod/trebuchet/CellLayout.java index f69b8ce..6f1d12d 100644 --- a/src/com/cyanogenmod/trebuchet/CellLayout.java +++ b/src/com/cyanogenmod/trebuchet/CellLayout.java @@ -17,9 +17,9 @@ package com.cyanogenmod.trebuchet; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -37,8 +37,10 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; +import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; +import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; @@ -531,6 +533,10 @@ public class CellLayout extends ViewGroup { return false; } + public void restoreInstanceState(SparseArray<Parcelable> states) { + dispatchRestoreInstanceState(states); + } + @Override public void cancelLongPress() { super.cancelLongPress(); diff --git a/src/com/cyanogenmod/trebuchet/DeferredHandler.java b/src/com/cyanogenmod/trebuchet/DeferredHandler.java index 48582f0..aeb82b9 100644 --- a/src/com/cyanogenmod/trebuchet/DeferredHandler.java +++ b/src/com/cyanogenmod/trebuchet/DeferredHandler.java @@ -98,6 +98,18 @@ public class DeferredHandler { } } + /** Runs all queued Runnables from the calling thread. */ + public void flush() { + LinkedList<Runnable> queue = new LinkedList<Runnable>(); + synchronized (mQueue) { + queue.addAll(mQueue); + mQueue.clear(); + } + for (Runnable r : queue) { + r.run(); + } + } + void scheduleNextLocked() { if (mQueue.size() > 0) { Runnable peek = mQueue.getFirst(); diff --git a/src/com/cyanogenmod/trebuchet/Launcher.java b/src/com/cyanogenmod/trebuchet/Launcher.java index a2e73f0..f32b11c 100644 --- a/src/com/cyanogenmod/trebuchet/Launcher.java +++ b/src/com/cyanogenmod/trebuchet/Launcher.java @@ -185,7 +185,7 @@ public final class Launcher extends Activity "com.android.launcher.toolbar_voice_search_icon"; /** The different states that Launcher can be in. */ - private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED }; + private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED }; private State mState = State.WORKSPACE; private AnimatorSet mStateAnimation; private AnimatorSet mDividerAnimator; @@ -233,6 +233,10 @@ public final class Launcher extends Activity private boolean mAutoAdvanceRunning = false; private Bundle mSavedState; + // We set the state in both onCreate and then onNewIntent in some cases, which causes both + // scroll issues (because the workspace may not have been measured yet) and extra work. + // Instead, just save the state that we need to restore Launcher to, and commit it in onResume. + private State mOnResumeState = State.NONE; private SpannableStringBuilder mDefaultKeySsb = null; @@ -243,6 +247,9 @@ public final class Launcher extends Activity private boolean mWaitingForResult; private boolean mOnResumeNeedsLoad; + // Keep track of whether the user has left launcher + private static boolean sPausedFromUserAction = false; + private Bundle mSavedInstanceState; private LauncherModel mModel; @@ -275,6 +282,8 @@ public final class Launcher extends Activity private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2]; + private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); + static final ArrayList<String> sDumpLogs = new ArrayList<String>(); // We only want to get the SharedPreferences once since it does an FS stat each time we get @@ -354,6 +363,11 @@ public final class Launcher extends Activity mHideIconLabels = PreferencesProvider.Interface.Homescreen.getHideIconLabels(this); mAutoRotate = PreferencesProvider.Interface.General.getAutoRotate(this, getResources().getBoolean(R.bool.allow_rotation)); + // 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"); @@ -373,7 +387,7 @@ public final class Launcher extends Activity // Update customization drawer _after_ restoring the states if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.onPackagesUpdated(); + mAppsCustomizeContent.onPackagesUpdated(true); } if (PROFILE_STARTUP) { @@ -381,7 +395,15 @@ public final class Launcher extends Activity } if (!mRestoring) { - mModel.startLoader(true); + if (sPausedFromUserAction) { + // If the user leaves launcher, then we should just load items asynchronously when + // they return. + mModel.startLoader(true, -1); + } else { + // We only load the page synchronously if the user rotates (or triggers a + // configuration change) while launcher is in the foreground + mModel.startLoader(true, mWorkspace.getCurrentPage()); + } } if (!mModel.isAllAppsLoaded()) { @@ -402,6 +424,11 @@ public final class Launcher extends Activity unlockScreenOrientation(true); } + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + sPausedFromUserAction = true; + } + private void updateGlobalIcons() { boolean searchVisible = false; boolean voiceVisible = false; @@ -687,6 +714,14 @@ public final class Launcher extends Activity protected void onResume() { super.onResume(); + // Restore the previous launcher state + if (mOnResumeState == State.WORKSPACE) { + showWorkspace(false); + } else if (mOnResumeState == State.APPS_CUSTOMIZE) { + showAllApps(false); + } + mOnResumeState = State.NONE; + // Process any items that were added while Launcher was away InstallShortcutReceiver.flushInstallQueue(this); @@ -695,9 +730,11 @@ public final class Launcher extends Activity if (preferencesChanged()) { android.os.Process.killProcess(android.os.Process.myPid()); } + + sPausedFromUserAction = false; if (mRestoring || mOnResumeNeedsLoad) { mWorkspaceLoading = true; - mModel.startLoader(true); + mModel.startLoader(true, -1); mRestoring = false; mOnResumeNeedsLoad = false; } @@ -838,7 +875,7 @@ public final class Launcher extends Activity State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); if (state == State.APPS_CUSTOMIZE) { - showAllApps(false); + mOnResumeState = State.APPS_CUSTOMIZE; } int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); @@ -874,10 +911,8 @@ public final class Launcher extends Activity if (mAppsCustomizeTabHost != null) { String curTab = savedState.getString("apps_customize_currentTab"); if (curTab != null) { - // We set this directly so that there is no delay before the tab is set - mAppsCustomizeContent.setContentType( + mAppsCustomizeTabHost.setContentTypeImmediate( mAppsCustomizeTabHost.getContentTypeForTabTag(curTab)); - mAppsCustomizeTabHost.setCurrentTabByTag(curTab); mAppsCustomizeContent.loadAssociatedPages( mAppsCustomizeContent.getCurrentPage()); } @@ -1387,7 +1422,14 @@ public final class Launcher extends Activity closeFolder(); exitSpringLoadedDragMode(); - showWorkspace(alreadyOnHome); + + // If we are already on home, then just animate back to the workspace, otherwise, just + // wait until onResume to set the state back to Workspace + if (alreadyOnHome) { + showWorkspace(true); + } else { + mOnResumeState = State.WORKSPACE; + } final View v = getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { @@ -1404,14 +1446,16 @@ public final class Launcher extends Activity } @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - // Do not call super here - mSavedInstanceState = savedInstanceState; + public void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + for (int page: mSynchronouslyBoundPages) { + mWorkspace.restoreInstanceStateForChild(page); + } } @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); + outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage()); super.onSaveInstanceState(outState); outState.putInt(RUNTIME_STATE, mState.ordinal()); @@ -1807,7 +1851,7 @@ public final class Launcher extends Activity @Override public void onBackPressed() { - if (mState == State.APPS_CUSTOMIZE) { + if (isAllAppsVisible()) { showWorkspace(true); } else if (mWorkspace.getOpenFolder() != null) { Folder openFolder = mWorkspace.getOpenFolder(); @@ -1880,7 +1924,7 @@ public final class Launcher extends Activity handleFolderClick(fi); } } else if (v == mAllAppsButton) { - if (mState == State.APPS_CUSTOMIZE) { + if (isAllAppsVisible()) { showWorkspace(true); } else { onClickAllAppsButton(v); @@ -2331,7 +2375,7 @@ public final class Launcher extends Activity // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. public boolean isAllAppsVisible() { - return (mState == State.APPS_CUSTOMIZE); + return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE); } public boolean isAllAppsButtonRank(int rank) { @@ -2358,7 +2402,7 @@ public final class Launcher extends Activity void disableWallpaperIfInAllApps() { // Only disable it if we are in all apps - if (mState == State.APPS_CUSTOMIZE) { + if (isAllAppsVisible()) { if (mAppsCustomizeTabHost != null && !mAppsCustomizeTabHost.isTransitioning()) { updateWallpaperVisibility(false); @@ -2799,7 +2843,7 @@ public final class Launcher extends Activity } void enterSpringLoadedDragMode() { - if (mState == State.APPS_CUSTOMIZE) { + if (isAllAppsVisible()) { hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null); hideDockDivider(); mState = State.APPS_CUSTOMIZE_SPRING_LOADED; @@ -2883,10 +2927,6 @@ public final class Launcher extends Activity // TODO } - public boolean isAllAppsCustomizeOpen() { - return mState == State.APPS_CUSTOMIZE; - } - /** * Shows the hotseat area. */ @@ -3381,6 +3421,10 @@ public final class Launcher extends Activity } } + public void onPageBoundSynchronously(int page) { + mSynchronouslyBoundPages.add(page); + } + /** * Callback saying that there aren't any more items to bind. * @@ -3396,10 +3440,7 @@ public final class Launcher extends Activity mSavedState = null; } - if (mSavedInstanceState != null) { - super.onRestoreInstanceState(mSavedInstanceState); - mSavedInstanceState = null; - } + mWorkspace.restoreInstanceStateForRemainingPages(); // If we received the result of any pending adds while the loader was running (e.g. the // widget configuration forced an orientation change), process them now. @@ -3522,23 +3563,30 @@ public final class Launcher extends Activity * Implementation of the method from LauncherModel.Callbacks. */ public void bindAllApplications(final ArrayList<ApplicationInfo> apps) { + Runnable setAllAppsRunnable = new Runnable() { + public void run() { + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.setApps(apps); + } + } + }; + // Remove the progress bar entirely; we could also make it GONE // but better to remove it since we know it's not going to be used View progressBar = mAppsCustomizeTabHost. findViewById(R.id.apps_customize_progress_bar); if (progressBar != null) { ((ViewGroup)progressBar.getParent()).removeView(progressBar); + + // We just post the call to setApps so the user sees the progress bar + // disappear-- otherwise, it just looks like the progress bar froze + // which doesn't look great + mAppsCustomizeTabHost.post(setAllAppsRunnable); + } else { + // If we did not initialize the spinner in onCreate, then we can directly set the + // list of applications without waiting for any progress bars views to be hidden. + setAllAppsRunnable.run(); } - // We just post the call to setApps so the user sees the progress bar - // disappear-- otherwise, it just looks like the progress bar froze - // which doesn't look great - mAppsCustomizeTabHost.post(new Runnable() { - public void run() { - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.setApps(apps); - } - } - }); } /** @@ -3593,7 +3641,7 @@ public final class Launcher extends Activity */ public void bindPackagesUpdated() { if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.onPackagesUpdated(); + mAppsCustomizeContent.onPackagesUpdated(false); } } diff --git a/src/com/cyanogenmod/trebuchet/LauncherModel.java b/src/com/cyanogenmod/trebuchet/LauncherModel.java index 94fbd29..6ada4de 100644 --- a/src/com/cyanogenmod/trebuchet/LauncherModel.java +++ b/src/com/cyanogenmod/trebuchet/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<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); + private WeakReference<Callbacks> 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<Long, ItemInfo> sItemsIdMap = new HashMap<Long, ItemInfo>(); + static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>(); - // 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<ItemInfo> sWorkspaceItems = new ArrayList<ItemInfo>(); + static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>(); - // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() - static final ArrayList<LauncherAppWidgetInfo> sAppWidgets = + // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() + static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); - // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>(); + // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() + static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>(); - // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database - static final HashMap<Object, byte[]> sDbIconCache = new HashMap<Object, byte[]>(); + // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database + static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>(); // </ only access in worker thread > @@ -140,6 +156,7 @@ public class LauncherModel extends BroadcastReceiver { public boolean isAllAppsVisible(); public boolean isAllAppsButtonRank(int rank); public void bindSearchablesChanged(); + public void onPageBoundSynchronously(int page); } LauncherModel(LauncherApplication app, IconCache iconCache) { @@ -158,6 +175,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 +210,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<ItemInfo> 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<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(sWorkspaceItems); - final ArrayList<ItemInfo> appWidgets = new ArrayList<ItemInfo>(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<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>(); + final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>(); + 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 +259,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 (!sWorkspaceItems.contains(modelItem)) { - sWorkspaceItems.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 (!sBgWorkspaceItems.contains(modelItem)) { + sBgWorkspaceItems.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 +491,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 +557,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 +589,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 +736,7 @@ public class LauncherModel extends BroadcastReceiver { } } if (runLoader) { - startLoader(false); + startLoader(false, -1); } } @@ -715,24 +754,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 +819,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<Object, CharSequence> mLabelCache; LoaderTask(Context context, boolean isLaunching) { @@ -802,7 +859,7 @@ public class LauncherModel extends BroadcastReceiver { } // Bind the workspace - bindWorkspace(); + bindWorkspace(-1); } private void waitForIdle() { @@ -839,7 +896,46 @@ 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). + + // The LauncherModel is static in the LauncherApplication and mHandler may have queued + // operations from the previous activity. We need to ensure that all queued operations + // are executed before any synchronous binding work is done. + mHandler.flush(); + + // 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 +991,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 +1007,7 @@ public class LauncherModel extends BroadcastReceiver { if (mLoaderTask == this) { mLoaderTask = null; } + mIsLoaderTaskRunning = false; } } @@ -1008,278 +1107,362 @@ 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(); - - final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); - - final Cursor c = contentResolver.query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); + synchronized (sBgLock) { + sBgWorkspaceItems.clear(); + sBgAppWidgets.clear(); + sBgFolders.clear(); + sBgItemsIdMap.clear(); + sBgDbIconCache.clear(); - // +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.MAX_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); + final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); - 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; - } + final Cursor c = contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, 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); + 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.MAX_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<ItemInfo> allWorkspaceItems, + ArrayList<ItemInfo> currentScreenItems, + ArrayList<ItemInfo> otherScreenItems) { + // Purge any null ItemInfos + Iterator<ItemInfo> 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<Long> itemsOnScreen = new HashSet<Long>(); + Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { + @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); } } } + } - 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.MAX_SCREEN_COUNT; s++) { - if (s > 0) { - line += " | "; - } - for (int x = 0; x < mCellCountX; x++) { - line += ((occupied[s][x][y] != null) ? "#" : "."); - } - } - Log.d(TAG, "[ " + line + " ]"); + /** Filters the set of widgets which are on the specified screen. */ + private void filterCurrentAppWidgets(int currentScreen, + ArrayList<LauncherAppWidgetInfo> appWidgets, + ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, + ArrayList<LauncherAppWidgetInfo> 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); + } + + 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<Long, ItemInfo> itemsIdMap, + HashMap<Long, FolderInfo> folders, + HashMap<Long, FolderInfo> currentScreenFolders, + HashMap<Long, FolderInfo> 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<ItemInfo> tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread(); - // Order the items for loading as follows: current workspace, hotseat, everything else - Collections.sort(tmpWorkspaceItems, new Comparator<ItemInfo>() { + /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to + * right) */ + private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { + // XXX: review this + Collections.sort(workspaceItems, new Comparator<ItemInfo>() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { int cellCountX = LauncherModel.getCellCountX(); @@ -1293,108 +1476,168 @@ public class LauncherModel extends BroadcastReceiver { return (int) (lr - rr); } }); - // Precondition: the items are ordered by page, screen - final ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); - 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); + } + + private void bindWorkspaceItems(final Callbacks oldCallbacks, + final ArrayList<ItemInfo> workspaceItems, + final ArrayList<LauncherAppWidgetInfo> appWidgets, + final HashMap<Long, FolderInfo> folders, + ArrayList<Runnable> deferredBindRunnables) { + + final boolean postOnMainThread = (deferredBindRunnables != null); + + // Bind the workspace items + int N = workspaceItems.size(); + for (int i = 0; i < N; i += ITEMS_CHUNK) { + final int start = i; + final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); + final Runnable r = new Runnable() { + @Override + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.bindItems(workspaceItems, start, start+chunkSize); + } + } + }; + if (postOnMainThread) { + deferredBindRunnables.add(r); } else { - workspaceItems.add(ii); + runOnMainThread(r); } } - // 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(); + // Bind the folders + if (!folders.isEmpty()) { + final Runnable r = new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.bindFolders(folders); + } } + }; + if (postOnMainThread) { + deferredBindRunnables.add(r); + } else { + runOnMainThread(r); } - }); + } - // Add the items to the workspace. - int N = workspaceItems.size(); - for (int i=0; i<N; i+=ITEMS_CHUNK) { - final int start = i; - final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); - mHandler.post(new Runnable() { + // Bind the widgets, one at a time + N = appWidgets.size(); + for (int i = 0; i < N; i++) { + final LauncherAppWidgetInfo widget = appWidgets.get(i); + final Runnable r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { - callbacks.bindItems(workspaceItems, start, start+chunkSize); + callbacks.bindAppWidget(widget); } } - }); + }; + if (postOnMainThread) { + deferredBindRunnables.add(r); + } else { + runOnMainThread(r); + } } - // Ensure that we don't use the same folders data structure on the main thread - final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders); - mHandler.post(new Runnable() { + } + + /** + * Binds all loaded data to actual views on the main thread. + */ + private void bindWorkspace(int synchronizeBindPage) { + final long t = SystemClock.uptimeMillis(); + Runnable r; + + // 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; + } + + final boolean isLoadingSynchronously = (synchronizeBindPage > -1); + final int currentScreen = isLoadingSynchronously ? 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<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); + ArrayList<LauncherAppWidgetInfo> appWidgets = + new ArrayList<LauncherAppWidgetInfo>(); + HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); + HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); + synchronized (sBgLock) { + workspaceItems.addAll(sBgWorkspaceItems); + appWidgets.addAll(sBgAppWidgets); + folders.putAll(sBgFolders); + itemsIdMap.putAll(sBgItemsIdMap); + } + + ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>(); + ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>(); + ArrayList<LauncherAppWidgetInfo> currentAppWidgets = + new ArrayList<LauncherAppWidgetInfo>(); + ArrayList<LauncherAppWidgetInfo> otherAppWidgets = + new ArrayList<LauncherAppWidgetInfo>(); + HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); + HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); + + // 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.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."); + callbacks.startBinding(); } } - }); - // 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<N; i++) { - final LauncherAppWidgetInfo widget = sAppWidgets.get(i); - if (widget.screen == currentScreen) { - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAppWidget(widget); - } - } - }); - } - } - // once for the other screens - for (int i=0; i<N; i++) { - final LauncherAppWidgetInfo widget = sAppWidgets.get(i); - if (widget.screen != currentScreen) { - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAppWidget(widget); - } + }; + runOnMainThread(r); + + // Load items on the current page + bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, + currentFolders, null); + if (isLoadingSynchronously) { + r = new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.onPageBoundSynchronously(currentScreen); } - }); - } + } + }; + runOnMainThread(r); } - // Tell the workspace that we're done. - mHandler.post(new Runnable() { + + // Load all the remaining pages (if we are loading synchronously, we want to defer this + // work until after the first render) + mDeferredBindRunnables.clear(); + bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, + (isLoadingSynchronously ? mDeferredBindRunnables : null)); + + // Tell the workspace that we're done binding items + r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.finishBindingItems(); } - } - }); - // Cleanup - mHandler.post(new Runnable() { - public void run() { + // If we're profiling, ensure this is the last thing in the queue. if (DEBUG_LOADERS) { Log.d(TAG, "bound workspace in " @@ -1403,7 +1646,12 @@ public class LauncherModel extends BroadcastReceiver { mIsLoadingAndBindingWorkspace = false; } - }); + }; + if (isLoadingSynchronously) { + mDeferredBindRunnables.add(r); + } else { + runOnMainThread(r); + } } private void loadAndBindAllApps() { @@ -1435,7 +1683,7 @@ public class LauncherModel extends BroadcastReceiver { @SuppressWarnings("unchecked") final ArrayList<ApplicationInfo> list = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone(); - mHandler.post(new Runnable() { + Runnable r = new Runnable() { public void run() { final long t = SystemClock.uptimeMillis(); final Callbacks callbacks = tryGetCallbacks(oldCallbacks); @@ -1447,8 +1695,13 @@ public class LauncherModel extends BroadcastReceiver { + (SystemClock.uptimeMillis()-t) + "ms"); } } - }); - + }; + boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid()); + if (oldCallbacks.isAllAppsVisible() && isRunningOnMainThread) { + r.run(); + } else { + mHandler.post(r); + } } private void loadAllAppsByBatch() { @@ -1566,12 +1819,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 +1956,13 @@ public class LauncherModel extends BroadcastReceiver { */ ArrayList<ShortcutInfo> getShortcutInfosForPackage(String packageName) { ArrayList<ShortcutInfo> infos = new ArrayList<ShortcutInfo>(); - 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/cyanogenmod/trebuchet/PagedViewWidget.java b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java index c1dfcdc..6fa0d39 100644 --- a/src/com/cyanogenmod/trebuchet/PagedViewWidget.java +++ b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -44,6 +45,7 @@ public class PagedViewWidget extends LinearLayout { boolean mShortPressTriggered = false; static PagedViewWidget sShortpressTarget = null; boolean mIsAppWidget; + private final Rect mOriginalImagePadding = new Rect(); public PagedViewWidget(Context context) { this(context, null); @@ -63,6 +65,17 @@ public class PagedViewWidget extends LinearLayout { setClipToPadding(false); } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + final ImageView image = (ImageView) findViewById(R.id.widget_preview); + mOriginalImagePadding.left = image.getPaddingLeft(); + mOriginalImagePadding.top = image.getPaddingTop(); + mOriginalImagePadding.right = image.getPaddingRight(); + mOriginalImagePadding.bottom = image.getPaddingBottom(); + } + public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) { sDeletePreviewsWhenDetachedFromWindow = value; } @@ -79,7 +92,7 @@ public class PagedViewWidget extends LinearLayout { preview.getBitmap().recycle(); } image.setImageDrawable(null); - } + } } } @@ -117,8 +130,8 @@ public class PagedViewWidget extends LinearLayout { public int[] getPreviewSize() { final ImageView i = (ImageView) findViewById(R.id.widget_preview); int[] maxSize = new int[2]; - maxSize[0] = i.getWidth() - i.getPaddingLeft() - i.getPaddingRight(); - maxSize[1] = i.getHeight() - i.getPaddingTop(); + maxSize[0] = i.getWidth() - mOriginalImagePadding.left - mOriginalImagePadding.right; + maxSize[1] = i.getHeight() - mOriginalImagePadding.top; return maxSize; } @@ -132,10 +145,10 @@ public class PagedViewWidget extends LinearLayout { // center horizontally int[] imageSize = getPreviewSize(); int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; - image.setPadding(image.getPaddingLeft() + centerAmount, - image.getPaddingTop(), - image.getPaddingRight(), - image.getPaddingBottom()); + image.setPadding(mOriginalImagePadding.left + centerAmount, + mOriginalImagePadding.top, + mOriginalImagePadding.right, + mOriginalImagePadding.bottom); } image.setAlpha(1f); image.mAllowRequestLayout = true; diff --git a/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java b/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java index 694c3c7..1f0befa 100644 --- a/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java +++ b/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java @@ -105,7 +105,7 @@ public abstract class PagedViewWithDraggableItems extends PagedView // Return early if we are still animating the pages if (mNextPage != INVALID_PAGE) return false; // When we have exited all apps or are in transition, disregard long clicks - if (!mLauncher.isAllAppsCustomizeOpen() || + if (!mLauncher.isAllAppsVisible() || mLauncher.getWorkspace().isSwitchingState()) return false; // Return if global dragging is not enabled if (!mLauncher.isDraggingEnabled()) return false; diff --git a/src/com/cyanogenmod/trebuchet/Workspace.java b/src/com/cyanogenmod/trebuchet/Workspace.java index a947cd4..cfe7912 100644 --- a/src/com/cyanogenmod/trebuchet/Workspace.java +++ b/src/com/cyanogenmod/trebuchet/Workspace.java @@ -49,6 +49,7 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.SparseArray; import android.view.Display; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -231,6 +232,9 @@ public class Workspace extends SmoothPagedView private int mLastReorderX = -1; private int mLastReorderY = -1; + private SparseArray<Parcelable> mSavedStates; + private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>(); + // These variables are used for storing the initial and final values during workspace animations private int mSavedScrollX; private float mSavedRotationY; @@ -1434,6 +1438,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() { @@ -3516,6 +3528,32 @@ public class Workspace extends SmoothPagedView } @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + // We don't dispatch restoreInstanceState to our children using this code path. + // Some pages will be restored immediately as their items are bound immediately, and + // others we will need to wait until after their items are bound. + mSavedStates = container; + } + + public void restoreInstanceStateForChild(int child) { + if (mSavedStates != null) { + mRestoredPages.add(child); + CellLayout cl = (CellLayout) getChildAt(child); + cl.restoreInstanceState(mSavedStates); + } + } + + public void restoreInstanceStateForRemainingPages() { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + if (!mRestoredPages.contains(i)) { + restoreInstanceStateForChild(i); + } + } + mRestoredPages.clear(); + } + + @Override public void scrollLeft() { if (!isSmall() && !mIsSwitchingState) { super.scrollLeft(); |