summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Kondik <shade@chemlab.org>2012-11-05 12:14:58 -0800
committerSteve Kondik <shade@chemlab.org>2012-11-05 12:14:58 -0800
commitf5baac88b85efa794ae7909f784538f379dd36f2 (patch)
treec55b82edcaae558d71fc12f856bb93d9ae8ce8dc
parent5bf25aecb90bd49ca3a8affea877e570b75baed6 (diff)
parent4124016ae5c8174aa53433894b896604ffa52c67 (diff)
downloadandroid_packages_apps_Trebuchet-f5baac88b85efa794ae7909f784538f379dd36f2.zip
android_packages_apps_Trebuchet-f5baac88b85efa794ae7909f784538f379dd36f2.tar.gz
android_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
-rw-r--r--res/layout-land/drop_target_bar.xml37
-rw-r--r--res/layout/drop_target_bar.xml (renamed from res/layout-port/drop_target_bar.xml)0
-rw-r--r--res/values-sw600dp-land/dimens.xml1
-rw-r--r--res/values-sw600dp/config.xml3
-rw-r--r--res/values-sw720dp/config.xml1
-rw-r--r--src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java24
-rw-r--r--src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java30
-rw-r--r--src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java19
-rw-r--r--src/com/cyanogenmod/trebuchet/CellLayout.java8
-rw-r--r--src/com/cyanogenmod/trebuchet/DeferredHandler.java12
-rw-r--r--src/com/cyanogenmod/trebuchet/Launcher.java122
-rw-r--r--src/com/cyanogenmod/trebuchet/LauncherModel.java1134
-rw-r--r--src/com/cyanogenmod/trebuchet/PagedViewWidget.java27
-rw-r--r--src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java2
-rw-r--r--src/com/cyanogenmod/trebuchet/Workspace.java38
15 files changed, 905 insertions, 553 deletions
diff --git a/res/layout-land/drop_target_bar.xml b/res/layout-land/drop_target_bar.xml
deleted file mode 100644
index f3fb400..0000000
--- a/res/layout-land/drop_target_bar.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/DropTargetButtonContainer"
- android:layout_weight="1">
- <!-- Delete target -->
- <com.cyanogenmod.trebuchet.DeleteDropTarget
- style="@style/DropTargetButton"
- android:id="@+id/delete_target_text"
- android:drawableTop="@drawable/remove_target_selector" />
- </FrameLayout>
- <FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/DropTargetButtonContainer"
- android:layout_weight="1">
- <!-- Info target -->
- <com.cyanogenmod.trebuchet.InfoDropTarget
- style="@style/DropTargetButton"
- android:id="@+id/info_target_text"
- android:drawableTop="@drawable/info_target_selector" />
- </FrameLayout>
-</merge> \ No newline at end of file
diff --git a/res/layout-port/drop_target_bar.xml b/res/layout/drop_target_bar.xml
index dbd1a29..dbd1a29 100644
--- a/res/layout-port/drop_target_bar.xml
+++ b/res/layout/drop_target_bar.xml
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index 1093729..4bf7519 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -31,6 +31,7 @@
<!-- QSB -->
<dimen name="qsb_bar_height">82dp</dimen>
+ <dimen name="toolbar_button_horizontal_padding">20dip</dimen>
<!-- Workspace -->
<dimen name="workspace_divider_padding_top">12dp</dimen>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 02789de..b67b9c1 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -1,9 +1,10 @@
<resources>
+ <bool name="allow_rotation">true</bool>
+
<integer name="cell_count_x">6</integer>
<integer name="cell_count_y">6</integer>
<integer name="hotseat_cell_count">7</integer>
<integer name="hotseat_all_apps_index">3</integer>
-
<!-- DragController -->
<integer name="config_flingToDeleteMinVelocity">-1000</integer>
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
index 97a6e9f..cdca805 100644
--- a/res/values-sw720dp/config.xml
+++ b/res/values-sw720dp/config.xml
@@ -1,7 +1,6 @@
<resources>
<bool name="config_largeHeap">true</bool>
<bool name="is_large_screen">true</bool>
- <bool name="allow_rotation">true</bool>
<!-- AllApps/Customize/AppsCustomize -->
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
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();