This version of the pager is best for use when there are a handful of
+ * typically more static fragments to be paged through, such as a set of tabs.
+ * The fragment of each page the user visits will be kept in memory, though its
+ * view hierarchy may be destroyed when not visible. This can result in using
+ * a significant amount of memory since fragment instances can hold on to an
+ * arbitrary amount of state. For larger sets of pages, consider
+ * {@link FragmentStatePagerAdapter}.
+ *
+ * When using FragmentPagerAdapter the host ViewPager must have a
+ * valid ID set.
+ *
+ * Subclasses only need to implement {@link #getItem(int)}
+ * and {@link #getCount()} to have a working adapter.
+ *
+ *
Here is an example implementation of a pager containing fragments of
+ * lists:
+ *
+ * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentPagerSupport.java
+ * complete}
+ *
+ *
The R.layout.fragment_pager
resource of the top-level fragment is:
+ *
+ * {@sample development/samples/Support4Demos/res/layout/fragment_pager.xml
+ * complete}
+ *
+ *
The R.layout.fragment_pager_list
resource containing each
+ * individual fragment's layout is:
+ *
+ * {@sample development/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * complete}
*/
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
@@ -44,11 +76,11 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
public abstract Fragment getItem(int position);
@Override
- public void startUpdate(View container) {
+ public void startUpdate(ViewGroup container) {
}
@Override
- public Object instantiateItem(View container, int position) {
+ public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
@@ -57,48 +89,50 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
String name = makeFragmentName(container.getId(), position);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
- if (DEBUG)
- Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
+ if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
- if (DEBUG)
- Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
- mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), position));
+ if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ mCurTransaction.add(container.getId(), fragment,
+ makeFragmentName(container.getId(), position));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
+ fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
- public void destroyItem(View container, int position, Object object) {
+ public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
- if (DEBUG)
- Log.v(TAG, "Detaching item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView());
- mCurTransaction.detach((Fragment) object);
+ if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
+ + " v=" + ((Fragment)object).getView());
+ mCurTransaction.detach((Fragment)object);
}
@Override
- public void setPrimaryItem(View container, int position, Object object) {
- Fragment fragment = (Fragment) object;
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
+ mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
+ fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
- public void finishUpdate(View container) {
+ public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
@@ -108,7 +142,7 @@ public abstract class FragmentPagerAdapter extends PagerAdapter {
@Override
public boolean isViewFromObject(View view, Object object) {
- return ((Fragment) object).getView() == view;
+ return ((Fragment)object).getView() == view;
}
@Override
diff --git a/actionbarsherlock/library/src/android/support/v4/app/FragmentStatePagerAdapter.java b/actionbarsherlock/library/src/android/support/v4/app/FragmentStatePagerAdapter.java
index 188544827..b82e54814 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -23,7 +23,44 @@ import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
+/**
+ * Implementation of {@link android.support.v4.view.PagerAdapter} that
+ * uses a {@link Fragment} to manage each page. This class also handles
+ * saving and restoring of fragment's state.
+ *
+ *
This version of the pager is more useful when there are a large number
+ * of pages, working more like a list view. When pages are not visible to
+ * the user, their entire fragment may be destroyed, only keeping the saved
+ * state of that fragment. This allows the pager to hold on to much less
+ * memory associated with each visited page as compared to
+ * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
+ * switching between pages.
+ *
+ *
When using FragmentPagerAdapter the host ViewPager must have a
+ * valid ID set.
+ *
+ * Subclasses only need to implement {@link #getItem(int)}
+ * and {@link #getCount()} to have a working adapter.
+ *
+ *
Here is an example implementation of a pager containing fragments of
+ * lists:
+ *
+ * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java
+ * complete}
+ *
+ *
The R.layout.fragment_pager
resource of the top-level fragment is:
+ *
+ * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml
+ * complete}
+ *
+ *
The R.layout.fragment_pager_list
resource containing each
+ * individual fragment's layout is:
+ *
+ * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml
+ * complete}
+ */
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = false;
@@ -45,11 +82,11 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
public abstract Fragment getItem(int position);
@Override
- public void startUpdate(View container) {
+ public void startUpdate(ViewGroup container) {
}
@Override
- public Object instantiateItem(View container, int position) {
+ public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
@@ -84,7 +121,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
- public void destroyItem(View container, int position, Object object) {
+ public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (mCurTransaction == null) {
@@ -102,7 +139,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
- public void setPrimaryItem(View container, int position, Object object) {
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
@@ -116,7 +153,7 @@ public abstract class FragmentStatePagerAdapter extends PagerAdapter {
}
@Override
- public void finishUpdate(View container) {
+ public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
diff --git a/actionbarsherlock/library/src/android/support/v4/app/FragmentTransaction.java b/actionbarsherlock/library/src/android/support/v4/app/FragmentTransaction.java
index 252873ae1..afc65c9d7 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/FragmentTransaction.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/FragmentTransaction.java
@@ -215,7 +215,7 @@ public abstract class FragmentTransaction {
/**
* Set the full title to show as a bread crumb when this transaction
- * is on the back stack, as used by {@link FragmentBreadCrumbs}.
+ * is on the back stack.
*
* @param res A string resource containing the title.
*/
@@ -230,7 +230,7 @@ public abstract class FragmentTransaction {
/**
* Set the short title to show as a bread crumb when this transaction
- * is on the back stack, as used by {@link FragmentBreadCrumbs}.
+ * is on the back stack.
*
* @param res A string resource containing the title.
*/
diff --git a/actionbarsherlock/library/src/android/support/v4/app/HCSparseArray.java b/actionbarsherlock/library/src/android/support/v4/app/HCSparseArray.java
index f6cf9fe26..50161256b 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/HCSparseArray.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/HCSparseArray.java
@@ -17,7 +17,8 @@
package android.support.v4.app;
/**
- * A copy of Honeycomb's SparseArray, only so we can have the removeAt() method.
+ * A copy of Honeycomb's {@link android.util.SparseArray}, that
+ * provides a removeAt() method.
*/
public class HCSparseArray {
private static final Object DELETED = new Object();
@@ -330,18 +331,6 @@ public class HCSparseArray {
return ~high;
}
- /*private void checkIntegrity() {
- for (int i = 1; i < mSize; i++) {
- if (mKeys[i] <= mKeys[i - 1]) {
- for (int j = 0; j < mSize; j++) {
- Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
- }
-
- throw new RuntimeException();
- }
- }
- }*/
-
static int idealByteArraySize(int need) {
for (int i = 4; i < 32; i++)
if (need <= (1 << i) - 12)
diff --git a/actionbarsherlock/library/src/android/support/v4/app/ListFragment.java b/actionbarsherlock/library/src/android/support/v4/app/ListFragment.java
index 7f8666fff..c901b7dae 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/ListFragment.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/ListFragment.java
@@ -113,13 +113,13 @@ public class ListFragment extends Fragment {
FrameLayout lframe = new FrameLayout(context);
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
- TextView tv = new TextView(context);
+ TextView tv = new TextView(getActivity());
tv.setId(INTERNAL_EMPTY_ID);
tv.setGravity(Gravity.CENTER);
lframe.addView(tv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
- ListView lv = new ListView(context);
+ ListView lv = new ListView(getActivity());
lv.setId(android.R.id.list);
lv.setDrawSelectorOnTop(false);
lframe.addView(lv, new FrameLayout.LayoutParams(
diff --git a/actionbarsherlock/library/src/android/support/v4/app/LoaderManager.java b/actionbarsherlock/library/src/android/support/v4/app/LoaderManager.java
index 69258ce8c..e5b18b15d 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/LoaderManager.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/LoaderManager.java
@@ -174,6 +174,12 @@ public abstract class LoaderManager {
public static void enableDebugLogging(boolean enabled) {
LoaderManagerImpl.DEBUG = enabled;
}
+
+ /**
+ * Returns true if any loaders managed are currently running and have not
+ * returned data to the application yet.
+ */
+ public boolean hasRunningLoaders() { return false; }
}
class LoaderManagerImpl extends LoaderManager {
@@ -398,6 +404,10 @@ class LoaderManagerImpl extends LoaderManager {
info.destroy();
mInactiveLoaders.remove(mId);
}
+
+ if (mActivity != null && !hasRunningLoaders()) {
+ mActivity.getInternalCallbacks().getFragments().startPendingDeferredFragments();
+ }
}
void callOnLoadFinished(Loader loader, Object data) {
@@ -657,6 +667,9 @@ class LoaderManagerImpl extends LoaderManager {
mInactiveLoaders.removeAt(idx);
info.destroy();
}
+ if (mActivity != null && !hasRunningLoaders()) {
+ mActivity.getInternalCallbacks().getFragments().startPendingDeferredFragments();
+ }
}
/**
@@ -800,4 +813,15 @@ class LoaderManagerImpl extends LoaderManager {
}
}
}
+
+ @Override
+ public boolean hasRunningLoaders() {
+ boolean loadersRunning = false;
+ final int count = mLoaders.size();
+ for (int i = 0; i < count; i++) {
+ final LoaderInfo li = mLoaders.valueAt(i);
+ loadersRunning |= li.mStarted && !li.mDeliveredData;
+ }
+ return loadersRunning;
+ }
}
diff --git a/actionbarsherlock/library/src/android/support/v4/app/NoSaveStateFrameLayout.java b/actionbarsherlock/library/src/android/support/v4/app/NoSaveStateFrameLayout.java
index a2177e32b..001bc9781 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/NoSaveStateFrameLayout.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/NoSaveStateFrameLayout.java
@@ -24,7 +24,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
/**
- * Pre-Honeycomb versions of the platform don't have View.setSaveFromParentEnabled(),
+ * Pre-Honeycomb versions of the platform don't have {@link View#setSaveFromParentEnabled(boolean)},
* so instead we insert this between the view and its parent.
*/
public class NoSaveStateFrameLayout extends FrameLayout {
diff --git a/actionbarsherlock/library/src/android/support/v4/app/ServiceCompat.java b/actionbarsherlock/library/src/android/support/v4/app/ServiceCompat.java
new file mode 100644
index 000000000..f644501c4
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/app/ServiceCompat.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.app;
+
+/**
+ * Helper for accessing features in {@link android.app.Service}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class ServiceCompat {
+
+ private ServiceCompat() {
+ /* Hide constructor */
+ }
+
+ /**
+ * Constant to return from {@link android.app.Service#onStartCommand}: if this
+ * service's process is killed while it is started (after returning from
+ * {@link android.app.Service#onStartCommand}), then leave it in the started
+ * state but don't retain this delivered intent. Later the system will try to
+ * re-create the service. Because it is in the started state, it will
+ * guarantee to call {@link android.app.Service#onStartCommand} after creating
+ * the new service instance; if there are not any pending start commands to be
+ * delivered to the service, it will be called with a null intent
+ * object, so you must take care to check for this.
+ *
+ * This mode makes sense for things that will be explicitly started
+ * and stopped to run for arbitrary periods of time, such as a service
+ * performing background music playback.
+ */
+ public static final int START_STICKY = 1;
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/app/SupportActivity.java b/actionbarsherlock/library/src/android/support/v4/app/SupportActivity.java
index 7bc29ff2d..40e203aad 100644
--- a/actionbarsherlock/library/src/android/support/v4/app/SupportActivity.java
+++ b/actionbarsherlock/library/src/android/support/v4/app/SupportActivity.java
@@ -64,7 +64,6 @@ import android.view.accessibility.AccessibilityEvent;
*/
public interface SupportActivity extends SherlockActivity {
public static abstract class InternalCallbacks {
- abstract void ensureSupportActionBarAttached();
abstract Handler getHandler();
abstract FragmentManagerImpl getFragments();
abstract LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create);
diff --git a/actionbarsherlock/library/src/android/support/v4/content/AsyncTaskLoader.java b/actionbarsherlock/library/src/android/support/v4/content/AsyncTaskLoader.java
index e52d0893b..74e74ced9 100644
--- a/actionbarsherlock/library/src/android/support/v4/content/AsyncTaskLoader.java
+++ b/actionbarsherlock/library/src/android/support/v4/content/AsyncTaskLoader.java
@@ -116,7 +116,7 @@ public abstract class AsyncTaskLoader extends Loader {
}
/**
- * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
+ * Attempt to cancel the current load task. See {@link android.os.AsyncTask#cancel(boolean)}
* for more info. Must be called on the main thread of the process.
*
* Cancelling is not an immediate operation, since the load is performed
diff --git a/actionbarsherlock/library/src/android/support/v4/content/CursorLoader.java b/actionbarsherlock/library/src/android/support/v4/content/CursorLoader.java
index 51ad8b54e..404d9238f 100644
--- a/actionbarsherlock/library/src/android/support/v4/content/CursorLoader.java
+++ b/actionbarsherlock/library/src/android/support/v4/content/CursorLoader.java
@@ -98,7 +98,7 @@ public class CursorLoader extends AsyncTaskLoader {
/**
* Creates a fully-specified CursorLoader. See
- * {@link ContentResolver#query(Uri, String[], String, String[], String)
+ * {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
* ContentResolver.query()} for documentation on the meaning of the
* parameters. These will be passed as-is to that call.
*/
diff --git a/actionbarsherlock/library/src/android/support/v4/content/IntentCompat.java b/actionbarsherlock/library/src/android/support/v4/content/IntentCompat.java
new file mode 100644
index 000000000..cbcd3f74a
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/content/IntentCompat.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.content;
+
+/**
+ * Helper for accessing features in {@link android.content.Intent}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class IntentCompat {
+
+ private IntentCompat() {
+ /* Hide constructor */
+ }
+
+ /**
+ * Broadcast Action: Resources for a set of packages (which were
+ * previously unavailable) are currently
+ * available since the media on which they exist is available.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * Note that the
+ * packages in this list do not receive this broadcast.
+ * The specified set of packages are now available on the system.
+ * Includes the following extras:
+ *
+ * {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources(were previously unavailable) are currently available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of uids of the
+ * packages whose resources(were previously unavailable)
+ * are currently available.
+ *
+ *
+ * This is a protected intent that can only be sent
+ * by the system.
+ */
+ public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
+
+ /**
+ * Broadcast Action: Resources for a set of packages are currently
+ * unavailable since the media on which they exist is unavailable.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * The specified set of packages can no longer be
+ * launched and are practically unavailable on the system.
+ *
Inclues the following extras:
+ *
+ * {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources are no longer available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of packages
+ * whose resources are no longer available.
+ *
+ *
+ * This is a protected intent that can only be sent
+ * by the system.
+ */
+ public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+
+ /**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
+ * and contains a string array of all of the components that have changed.
+ */
+ public static final String EXTRA_CHANGED_PACKAGE_LIST =
+ "android.intent.extra.changed_package_list";
+
+ /**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
+ * and contains an integer array of uids of all of the components
+ * that have changed.
+ */
+ public static final String EXTRA_CHANGED_UID_LIST =
+ "android.intent.extra.changed_uid_list";
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/content/Loader.java b/actionbarsherlock/library/src/android/support/v4/content/Loader.java
index b9f2c6a87..007096a1d 100644
--- a/actionbarsherlock/library/src/android/support/v4/content/Loader.java
+++ b/actionbarsherlock/library/src/android/support/v4/content/Loader.java
@@ -40,6 +40,13 @@ public class Loader {
boolean mReset = true;
boolean mContentChanged = false;
+ /**
+ * An implementation of a ContentObserver that takes care of connecting
+ * it to the Loader to have the loader re-load its data when the observer
+ * is told it has changed. You do not normally need to use this yourself;
+ * it is used for you by {@link android.support.v4.content.CursorLoader}
+ * to take care of executing an update when the cursor's backing data changes.
+ */
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
@@ -56,6 +63,14 @@ public class Loader {
}
}
+ /**
+ * Interface that is implemented to discover when a Loader has finished
+ * loading its data. You do not normally need to implement this yourself;
+ * it is used in the implementation of {@link android.support.v4.app.LoaderManager}
+ * to find out when a Loader it is managing has completed so that this can
+ * be reported to its client. This interface should only be used if a
+ * Loader is not being used in conjunction with LoaderManager.
+ */
public interface OnLoadCompleteListener {
/**
* Called on the thread that created the Loader when the load is complete.
diff --git a/actionbarsherlock/library/src/android/support/v4/content/ModernAsyncTask.java b/actionbarsherlock/library/src/android/support/v4/content/ModernAsyncTask.java
index 30d97dca2..1e7229a9d 100644
--- a/actionbarsherlock/library/src/android/support/v4/content/ModernAsyncTask.java
+++ b/actionbarsherlock/library/src/android/support/v4/content/ModernAsyncTask.java
@@ -35,12 +35,16 @@ import android.os.Message;
import android.os.Process;
/**
- * Copy of the required parts of AsyncTask from Android 3.0 that is needed
- * to support AsyncTaskLoader. We use this rather than the one from the platform
+ * Copy of the required parts of {@link android.os.AsyncTask} from Android 3.0 that is
+ * needed to support AsyncTaskLoader. We use this rather than the one from the platform
* because we rely on some subtle behavior of AsyncTask that is not reliable on
* older platforms.
+ *
+ * Note that for now this is not publicly available because it is not a
+ * complete implementation, only sufficient for the needs of
+ * {@link AsyncTaskLoader}.
*/
-public abstract class ModernAsyncTask {
+abstract class ModernAsyncTask {
private static final String LOG_TAG = "AsyncTask";
private static final int CORE_POOL_SIZE = 5;
@@ -92,7 +96,7 @@ public abstract class ModernAsyncTask {
*/
RUNNING,
/**
- * Indicates that {@link AsyncTask#onPostExecute} has finished.
+ * Indicates that {@link android.os.AsyncTask#onPostExecute(Object)} has finished.
*/
FINISHED,
}
@@ -358,7 +362,8 @@ public abstract class ModernAsyncTask {
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
- * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ * {@link android.os.AsyncTask.Status#RUNNING} or
+ * {@link android.os.AsyncTask.Status#FINISHED}.
*/
public final ModernAsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
@@ -380,9 +385,7 @@ public abstract class ModernAsyncTask {
* there are no guarantees on the order of the modifications.
* Without careful work it is possible in rare cases for the newer version
* of the data to be over-written by an older one, leading to obscure data
- * loss and stability issues. Such changes are best
- * executed in serial; to guarantee such work is serialized regardless of
- * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
+ * loss and stability issues.
*
* This method must be invoked on the UI thread.
*
@@ -393,7 +396,8 @@ public abstract class ModernAsyncTask {
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
- * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ * {@link android.os.AsyncTask.Status#RUNNING}
+ * or {@link android.os.AsyncTask.Status#FINISHED}.
*/
public final ModernAsyncTask executeOnExecutor(Executor exec,
Params... params) {
diff --git a/actionbarsherlock/library/src/android/support/v4/content/pm/ActivityInfoCompat.java b/actionbarsherlock/library/src/android/support/v4/content/pm/ActivityInfoCompat.java
new file mode 100644
index 000000000..5c279236d
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/content/pm/ActivityInfoCompat.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.content.pm;
+
+/**
+ * Helper for accessing features in {@link android.content.pm.ActivityInfo}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class ActivityInfoCompat {
+
+ private ActivityInfoCompat() {
+ /* Hide constructor */
+ }
+
+ /**
+ * Bit in ActivityInfo#configChanges that indicates that the
+ * activity can itself handle the ui mode. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_UI_MODE = 0x0200;
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/database/DatabaseUtilsCompat.java b/actionbarsherlock/library/src/android/support/v4/database/DatabaseUtilsCompat.java
new file mode 100644
index 000000000..9a825e80f
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/database/DatabaseUtilsCompat.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.database;
+
+import android.text.TextUtils;
+
+/**
+ * Helper for accessing features in {@link android.database.DatabaseUtils}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class DatabaseUtilsCompat {
+
+ private DatabaseUtilsCompat() {
+ /* Hide constructor */
+ }
+
+ /**
+ * Concatenates two SQL WHERE clauses, handling empty or null values.
+ */
+ public static String concatenateWhere(String a, String b) {
+ if (TextUtils.isEmpty(a)) {
+ return b;
+ }
+ if (TextUtils.isEmpty(b)) {
+ return a;
+ }
+
+ return "(" + a + ") AND (" + b + ")";
+ }
+
+ /**
+ * Appends one set of selection args to another. This is useful when adding a selection
+ * argument to a user provided set.
+ */
+ public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
+ if (originalValues == null || originalValues.length == 0) {
+ return newValues;
+ }
+ String[] result = new String[originalValues.length + newValues.length ];
+ System.arraycopy(originalValues, 0, result, 0, originalValues.length);
+ System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
+ return result;
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompat.java b/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompat.java
index 714af3374..40ad994a8 100644
--- a/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompat.java
@@ -19,7 +19,18 @@ package android.support.v4.os;
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * Helper for accessing features in {@link android.os.Parcelable}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
public class ParcelableCompat {
+
+ /**
+ * Factory method for {@link Parcelable.Creator}.
+ *
+ * @param callbacks Creator callbacks implementation.
+ * @return New creator.
+ */
public static Parcelable.Creator newCreator(
ParcelableCompatCreatorCallbacks callbacks) {
if (android.os.Build.VERSION.SDK_INT >= 13) {
diff --git a/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java b/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
index 8678cfb75..573b7500e 100644
--- a/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
+++ b/actionbarsherlock/library/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
@@ -18,7 +18,29 @@ package android.support.v4.os;
import android.os.Parcel;
+/**
+ * Callbacks a {@link Parcelable} creator should implement.
+ */
public interface ParcelableCompatCreatorCallbacks {
+
+ /**
+ * Create a new instance of the Parcelable class, instantiating it
+ * from the given Parcel whose data had previously been written by
+ * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
+ * using the given ClassLoader.
+ *
+ * @param in The Parcel to read the object's data from.
+ * @param loader The ClassLoader that this object is being created in.
+ * @return Returns a new instance of the Parcelable class.
+ */
public T createFromParcel(Parcel in, ClassLoader loader);
+
+ /**
+ * Create a new array of the Parcelable class.
+ *
+ * @param size Size of the array.
+ * @return Returns an array of the Parcelable class, with every entry
+ * initialized to null.
+ */
public T[] newArray(int size);
}
diff --git a/actionbarsherlock/library/src/android/support/v4/util/DebugUtils.java b/actionbarsherlock/library/src/android/support/v4/util/DebugUtils.java
index 16c63a9f0..9d4f14ee7 100644
--- a/actionbarsherlock/library/src/android/support/v4/util/DebugUtils.java
+++ b/actionbarsherlock/library/src/android/support/v4/util/DebugUtils.java
@@ -17,9 +17,13 @@
package android.support.v4.util;
/**
- * Useful debugging utilities that are not available on all versions of Android.
+ * Helper for accessing features in {@link android.util.DebugUtils}
+ * introduced after API level 4 in a backwards compatible fashion.
+ *
+ * @hide
*/
public class DebugUtils {
+
public static void buildShortClassTag(Object cls, StringBuilder out) {
if (cls == null) {
out.append("null");
diff --git a/actionbarsherlock/library/src/android/support/v4/util/LogWriter.java b/actionbarsherlock/library/src/android/support/v4/util/LogWriter.java
index 3b9d12b04..53453e322 100644
--- a/actionbarsherlock/library/src/android/support/v4/util/LogWriter.java
+++ b/actionbarsherlock/library/src/android/support/v4/util/LogWriter.java
@@ -21,7 +21,10 @@ import android.util.Log;
import java.io.Writer;
/**
- * Useful logging utility that is not available on all versions of Android.
+ * Helper for accessing features in {@link android.util.LogWriter}
+ * introduced after API level 4 in a backwards compatible fashion.
+ *
+ * @hide
*/
public class LogWriter extends Writer {
private final String mTag;
@@ -31,12 +34,6 @@ public class LogWriter extends Writer {
* Create a new Writer that sends to the log with the given priority
* and tag.
*
- * @param priority The desired log priority:
- * {@link android.util.Log#VERBOSE Log.VERBOSE},
- * {@link android.util.Log#DEBUG Log.DEBUG},
- * {@link android.util.Log#INFO Log.INFO},
- * {@link android.util.Log#WARN Log.WARN}, or
- * {@link android.util.Log#ERROR Log.ERROR}.
* @param tag A string tag to associate with each printed log statement.
*/
public LogWriter(String tag) {
diff --git a/actionbarsherlock/library/src/android/support/v4/util/LruCache.java b/actionbarsherlock/library/src/android/support/v4/util/LruCache.java
index a7a2ee276..84806e375 100644
--- a/actionbarsherlock/library/src/android/support/v4/util/LruCache.java
+++ b/actionbarsherlock/library/src/android/support/v4/util/LruCache.java
@@ -20,7 +20,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
- * Static library version of {@code android.util.LruCache}. Used to write apps
+ * Static library version of {@link android.util.LruCache}. Used to write apps
* that run on API levels prior to 12. When running on API level 12 or above,
* this implementation is still used; it does not try to switch to the
* framework's implementation. See the framework SDK documentation for a class
diff --git a/actionbarsherlock/library/src/android/support/v4/util/TimeUtils.java b/actionbarsherlock/library/src/android/support/v4/util/TimeUtils.java
index 65ac8544e..202acd4f9 100644
--- a/actionbarsherlock/library/src/android/support/v4/util/TimeUtils.java
+++ b/actionbarsherlock/library/src/android/support/v4/util/TimeUtils.java
@@ -19,7 +19,10 @@ package android.support.v4.util;
import java.io.PrintWriter;
/**
- * Useful time utilities that are not available on all versions of Android.
+ * Helper for accessing features in {@link android.util.TimeUtils}
+ * introduced after API level 4 in a backwards compatible fashion.
+ *
+ * @hide
*/
public class TimeUtils {
/** @hide Field length that can hold 999 days of time */
diff --git a/actionbarsherlock/library/src/android/support/v4/view/AccessibilityDelegateCompat.java b/actionbarsherlock/library/src/android/support/v4/view/AccessibilityDelegateCompat.java
index c70b94f8c..c5704ef27 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/AccessibilityDelegateCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/AccessibilityDelegateCompat.java
@@ -22,7 +22,8 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
- * Helper for accessing AccessibilityDelegate from newer platform versions.
+ * Helper for accessing {@link View.AccessibilityDelegate} introduced after
+ * API level 4 in a backwards compatible fashion.
*/
public class AccessibilityDelegateCompat {
@@ -33,7 +34,8 @@ public class AccessibilityDelegateCompat {
AccessibilityEvent event);
public void onInitializeAccessibilityEvent(Object delegate, View host,
AccessibilityEvent event);
- public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info);
+ public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
+ AccessibilityNodeInfoCompat info);
public void onPopulateAccessibilityEvent(Object delegate, View host,
AccessibilityEvent event);
public boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, View child,
@@ -62,7 +64,8 @@ public class AccessibilityDelegateCompat {
}
- public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info) {
+ public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
+ AccessibilityNodeInfoCompat info) {
}
@@ -150,8 +153,10 @@ public class AccessibilityDelegateCompat {
}
@Override
- public void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info) {
- AccessibilityDelegateCompatIcs.onInitializeAccessibilityNodeInfo(delegate, host, info);
+ public void onInitializeAccessibilityNodeInfo(Object delegate, View host,
+ AccessibilityNodeInfoCompat info) {
+ AccessibilityDelegateCompatIcs.onInitializeAccessibilityNodeInfo(delegate, host,
+ info.getImpl());
}
@Override
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ActionMode.java b/actionbarsherlock/library/src/android/support/v4/view/ActionMode.java
index c84dedb19..9612220e7 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ActionMode.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ActionMode.java
@@ -16,6 +16,8 @@
package android.support.v4.view;
+import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
+
import android.view.View;
/**
@@ -107,11 +109,11 @@ public abstract class ActionMode {
public abstract Menu getMenu();
/**
- * Returns a {@link MenuInflater} with the ActionMode's context.
+ * Returns a {@link MenuInflaterImpl} with the ActionMode's context.
*
* @return Menu inflater.
*/
- public abstract MenuInflater getMenuInflater();
+ public abstract MenuInflaterImpl getMenuInflater();
/**
* Returns the current subtitle of this action mode.
diff --git a/actionbarsherlock/library/src/android/support/v4/view/KeyEventCompat.java b/actionbarsherlock/library/src/android/support/v4/view/KeyEventCompat.java
index 0967592d6..863875712 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/KeyEventCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/KeyEventCompat.java
@@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.KeyEvent;
/**
- * Helper for accessing newer features in KeyEvent.
+ * Helper for accessing features in {@link KeyEvent} introduced after
+ * API level 4 in a backwards compatible fashion.
*/
public class KeyEventCompat {
/**
diff --git a/actionbarsherlock/library/src/android/support/v4/view/Menu.java b/actionbarsherlock/library/src/android/support/v4/view/Menu.java
index 39a697181..702a0fffa 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/Menu.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/Menu.java
@@ -1,6 +1,77 @@
package android.support.v4.view;
public interface Menu extends android.view.Menu {
+
+ /**
+ * This is the part of an order integer that the user can provide.
+ * @hide
+ */
+ static final int USER_MASK = 0x0000ffff;
+ /**
+ * Bit shift of the user portion of the order integer.
+ * @hide
+ */
+ static final int USER_SHIFT = 0;
+
+ /**
+ * This is the part of an order integer that supplies the category of the
+ * item.
+ * @hide
+ */
+ static final int CATEGORY_MASK = 0xffff0000;
+ /**
+ * Bit shift of the category portion of the order integer.
+ * @hide
+ */
+ static final int CATEGORY_SHIFT = 16;
+
+ /**
+ * Value to use for group and item identifier integers when you don't care
+ * about them.
+ */
+ static final int NONE = 0;
+
+ /**
+ * First value for group and item identifier integers.
+ */
+ static final int FIRST = 1;
+
+ // Implementation note: Keep these CATEGORY_* in sync with the category enum
+ // in attrs.xml
+
+ /**
+ * Category code for the order integer for items/groups that are part of a
+ * container -- or/add this with your base value.
+ */
+ static final int CATEGORY_CONTAINER = 0x00010000;
+
+ /**
+ * Category code for the order integer for items/groups that are provided by
+ * the system -- or/add this with your base value.
+ */
+ static final int CATEGORY_SYSTEM = 0x00020000;
+
+ /**
+ * Category code for the order integer for items/groups that are
+ * user-supplied secondary (infrequently used) options -- or/add this with
+ * your base value.
+ */
+ static final int CATEGORY_SECONDARY = 0x00030000;
+
+ /**
+ * Category code for the order integer for items/groups that are
+ * alternative actions on the data that is currently displayed -- or/add
+ * this with your base value.
+ */
+ static final int CATEGORY_ALTERNATIVE = 0x00040000;
+
+ /**
+ * Flag for {@link #addIntentOptions}: if set, do not automatically remove
+ * any existing menu items in the same group.
+ */
+ static final int FLAG_APPEND_TO_GROUP = 0x0001;
+
+
@Override
MenuItem add(CharSequence title);
diff --git a/actionbarsherlock/library/src/android/support/v4/view/MenuCompat.java b/actionbarsherlock/library/src/android/support/v4/view/MenuCompat.java
new file mode 100644
index 000000000..a424ca90f
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/view/MenuCompat.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.view;
+
+import android.view.MenuItem;
+
+/**
+ * Use {@link FragmentActivity}, {@link Menu}, and {@link MenuItem}.
+ */
+@Deprecated
+public class MenuCompat {
+
+ /**
+ * Interface for the full API.
+ */
+ interface MenuVersionImpl {
+ public boolean setShowAsAction(MenuItem item, int actionEnum);
+ }
+
+ /**
+ * Interface implementation that doesn't use anything about v4 APIs.
+ */
+ static class BaseMenuVersionImpl implements MenuVersionImpl {
+ @Override
+ public boolean setShowAsAction(MenuItem item, int actionEnum) {
+ return false;
+ }
+ }
+
+ /**
+ * Interface implementation for devices with at least v11 APIs.
+ */
+ static class HoneycombMenuVersionImpl implements MenuVersionImpl {
+ @Override
+ public boolean setShowAsAction(MenuItem item, int actionEnum) {
+ MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
+ return true;
+ }
+ }
+
+ /**
+ * Select the correct implementation to use for the current platform.
+ */
+ static final MenuVersionImpl IMPL;
+ static {
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ IMPL = new HoneycombMenuVersionImpl();
+ } else {
+ IMPL = new BaseMenuVersionImpl();
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ /**
+ * Call {@link MenuItem#setShowAsAction(int) MenuItem.setShowAsAction()}.
+ * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} device,
+ * does nothing and returns false. Otherwise returns true.
+ *
+ * @deprecated Use {@link MenuItemCompat#setShowAsAction(MenuItem, int)
+ * MenuItemCompat.setShowAsAction(MenuItem, int)}
+ */
+ @Deprecated
+ public static boolean setShowAsAction(MenuItem item, int actionEnum) {
+ return IMPL.setShowAsAction(item, actionEnum);
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompat.java b/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompat.java
new file mode 100644
index 000000000..797d79bc0
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompat.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.view;
+
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * @Deprecated Use {@link FragmentActivity}, {@link Menu}, and {@link MenuItem}.
+ */
+@Deprecated
+public class MenuItemCompat {
+
+ /**
+ * @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_NEVER}.
+ */
+ @Deprecated
+ public static final int SHOW_AS_ACTION_NEVER = 0;
+
+ /**
+ * @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_IF_ROOM}.
+ */
+ @Deprecated
+ public static final int SHOW_AS_ACTION_IF_ROOM = 1;
+
+ /**
+ * @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_ALWAYS}.
+ */
+ @Deprecated
+ public static final int SHOW_AS_ACTION_ALWAYS = 2;
+
+ /**
+ * @Deprecated Use {@link MenuItem#SHOW_AS_ACTION_WITH_TEXT}.
+ */
+ @Deprecated
+ public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+ /**
+ * This item's action view collapses to a normal menu item.
+ * When expanded, the action view temporarily takes over
+ * a larger segment of its container.
+ */
+ public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
+
+ /**
+ * Interface for the full API.
+ */
+ interface MenuVersionImpl {
+ public boolean setShowAsAction(MenuItem item, int actionEnum);
+ public MenuItem setActionView(MenuItem item, View view);
+ }
+
+ /**
+ * Interface implementation that doesn't use anything about v4 APIs.
+ */
+ static class BaseMenuVersionImpl implements MenuVersionImpl {
+ @Override
+ public boolean setShowAsAction(MenuItem item, int actionEnum) {
+ return false;
+ }
+
+ @Override
+ public MenuItem setActionView(MenuItem item, View view) {
+ return item;
+ }
+ }
+
+ /**
+ * Interface implementation for devices with at least v11 APIs.
+ */
+ static class HoneycombMenuVersionImpl implements MenuVersionImpl {
+ @Override
+ public boolean setShowAsAction(MenuItem item, int actionEnum) {
+ MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
+ return true;
+ }
+ @Override
+ public MenuItem setActionView(MenuItem item, View view) {
+ return MenuItemCompatHoneycomb.setActionView(item, view);
+ }
+ }
+
+ /**
+ * Select the correct implementation to use for the current platform.
+ */
+ static final MenuVersionImpl IMPL;
+ static {
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ IMPL = new HoneycombMenuVersionImpl();
+ } else {
+ IMPL = new BaseMenuVersionImpl();
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ /** @Deprecated Use {@link MenuItem#setShowAsAction(int)}. */
+ @Deprecated
+ public static boolean setShowAsAction(MenuItem item, int actionEnum) {
+ return IMPL.setShowAsAction(item, actionEnum);
+ }
+
+ /** @Deprecated Use {@link MenuItem#setActionView(int)}. */
+ @Deprecated
+ public static MenuItem setActionView(MenuItem item, View view) {
+ return IMPL.setActionView(item, view);
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompatHoneycomb.java b/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompatHoneycomb.java
new file mode 100644
index 000000000..f8d874edc
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/view/MenuItemCompatHoneycomb.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.view;
+
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * Implementation of menu compatibility that can call Honeycomb APIs.
+ */
+class MenuItemCompatHoneycomb {
+ public static void setShowAsAction(MenuItem item, int actionEnum) {
+ item.setShowAsAction(actionEnum);
+ }
+
+ public static MenuItem setActionView(MenuItem item, View view) {
+ return item.setActionView(view);
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/MotionEventCompat.java b/actionbarsherlock/library/src/android/support/v4/view/MotionEventCompat.java
index e88ab63c7..e6acab590 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/MotionEventCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/MotionEventCompat.java
@@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.MotionEvent;
/**
- * Helper for accessing newer features in MotionEvent.
+ * Helper for accessing features in {@link MotionEvent} introduced
+ * after API level 4 in a backwards compatible fashion.
*/
public class MotionEventCompat {
/**
diff --git a/actionbarsherlock/library/src/android/support/v4/view/PagerAdapter.java b/actionbarsherlock/library/src/android/support/v4/view/PagerAdapter.java
index 0be93a0d5..6fd0e2b4d 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/PagerAdapter.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/PagerAdapter.java
@@ -16,8 +16,11 @@
package android.support.v4.view;
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
import android.os.Parcelable;
import android.view.View;
+import android.view.ViewGroup;
/**
* Base class providing the adapter to populate pages inside of
@@ -25,56 +28,169 @@ import android.view.View;
* specific implementation of this, such as
* {@link android.support.v4.app.FragmentPagerAdapter} or
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ *
+ * When you implement a PagerAdapter, you must override the following methods
+ * at minimum:
+ *
+ * {@link #instantiateItem(ViewGroup, int)}
+ * {@link #destroyItem(ViewGroup, int, Object)}
+ * {@link #getCount()}
+ * {@link #isViewFromObject(View, Object)}
+ *
+ *
+ * PagerAdapter is more general than the adapters used for
+ * {@link android.widget.AdapterView AdapterViews}. Instead of providing a
+ * View recycling mechanism directly ViewPager uses callbacks to indicate the
+ * steps taken during an update. A PagerAdapter may implement a form of View
+ * recycling if desired or use a more sophisticated method of managing page
+ * Views such as Fragment transactions where each page is represented by its
+ * own Fragment.
+ *
+ * ViewPager associates each page with a key Object instead of working with
+ * Views directly. This key is used to track and uniquely identify a given page
+ * independent of its position in the adapter. A call to the PagerAdapter method
+ * {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager
+ * are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)}
+ * and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end
+ * of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}.
+ * By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views
+ * associated with the key objects returned by
+ * {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to
+ * the parent ViewGroup passed to these methods and the views associated with
+ * the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem}
+ * should be removed. The method {@link #isViewFromObject(View, Object)} identifies
+ * whether a page View is associated with a given key object.
+ *
+ * A very simple PagerAdapter may choose to use the page Views themselves
+ * as key objects, returning them from {@link #instantiateItem(ViewGroup, int)}
+ * after creation and adding them to the parent ViewGroup. A matching
+ * {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the
+ * View from the parent ViewGroup and {@link #isViewFromObject(View, Object)}
+ * could be implemented as return view == object;
.
+ *
+ * PagerAdapter supports data set changes. Data set changes must occur on the
+ * main thread and must end with a call to {@link #notifyDataSetChanged()} similar
+ * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
+ * set change may involve pages being added, removed, or changing position. The
+ * ViewPager will keep the current page active provided the adapter implements
+ * the method {@link #getItemPosition(Object)}.
*/
public abstract class PagerAdapter {
- private DataSetObserver mObserver;
+ private DataSetObservable mObservable = new DataSetObservable();
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
/**
- * Used to watch for changes within the adapter.
+ * Return the number of views available.
*/
- interface DataSetObserver {
- public void onDataSetChanged();
+ public abstract int getCount();
+
+ /**
+ * Called when a change in the shown pages is going to start being made.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ */
+ public void startUpdate(ViewGroup container) {
+ startUpdate((View) container);
}
/**
- * Return the number of views available.
+ * Create the page for the given position. The adapter is responsible
+ * for adding the view to the container given here, although it only
+ * must ensure this is done by the time it returns from
+ * {@link #finishUpdate(ViewGroup)}.
+ *
+ * @param container The containing View in which the page will be shown.
+ * @param position The page position to be instantiated.
+ * @return Returns an Object representing the new page. This does not
+ * need to be a View, but can be some other container of the page.
*/
- public abstract int getCount();
+ public Object instantiateItem(ViewGroup container, int position) {
+ return instantiateItem((View) container, position);
+ }
+
+ /**
+ * Remove a page for the given position. The adapter is responsible
+ * for removing the view from its container, although it only must ensure
+ * this is done by the time it returns from {@link #finishUpdate(ViewGroup)}.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position to be removed.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(View, int)}.
+ */
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ destroyItem((View) container, position, object);
+ }
+
+ /**
+ * Called to inform the adapter of which item is currently considered to
+ * be the "primary", that is the one show to the user as the current page.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position that is now the primary.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(View, int)}.
+ */
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ setPrimaryItem((View) container, position, object);
+ }
+
+ /**
+ * Called when the a change in the shown pages has been completed. At this
+ * point you must ensure that all of the pages have actually been added or
+ * removed from the container as appropriate.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ */
+ public void finishUpdate(ViewGroup container) {
+ finishUpdate((View) container);
+ }
/**
* Called when a change in the shown pages is going to start being made.
* @param container The containing View which is displaying this adapter's
* page views.
+ *
+ * @deprecated Use {@link #startUpdate(ViewGroup)}
*/
- public abstract void startUpdate(View container);
+ public void startUpdate(View container) {
+ }
/**
* Create the page for the given position. The adapter is responsible
* for adding the view to the container given here, although it only
* must ensure this is done by the time it returns from
- * {@link #finishUpdate()}.
+ * {@link #finishUpdate(ViewGroup)}.
*
* @param container The containing View in which the page will be shown.
* @param position The page position to be instantiated.
* @return Returns an Object representing the new page. This does not
* need to be a View, but can be some other container of the page.
+ *
+ * @deprecated Use {@link #instantiateItem(ViewGroup, int)}
*/
- public abstract Object instantiateItem(View container, int position);
+ public Object instantiateItem(View container, int position) {
+ throw new UnsupportedOperationException(
+ "Required method instantiateItem was not overridden");
+ }
/**
* Remove a page for the given position. The adapter is responsible
* for removing the view from its container, although it only must ensure
- * this is done by the time it returns from {@link #finishUpdate()}.
+ * this is done by the time it returns from {@link #finishUpdate(View)}.
*
* @param container The containing View from which the page will be removed.
* @param position The page position to be removed.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
+ *
+ * @deprecated Use {@link #destroyItem(ViewGroup, int, Object)}
*/
- public abstract void destroyItem(View container, int position, Object object);
+ public void destroyItem(View container, int position, Object object) {
+ throw new UnsupportedOperationException("Required method destroyItem was not overridden");
+ }
/**
* Called to inform the adapter of which item is currently considered to
@@ -84,6 +200,8 @@ public abstract class PagerAdapter {
* @param position The page position that is now the primary.
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
+ *
+ * @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
public void setPrimaryItem(View container, int position, Object object) {
}
@@ -94,14 +212,42 @@ public abstract class PagerAdapter {
* removed from the container as appropriate.
* @param container The containing View which is displaying this adapter's
* page views.
+ *
+ * @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
- public abstract void finishUpdate(View container);
+ public void finishUpdate(View container) {
+ }
+ /**
+ * Determines whether a page View is associated with a specific key object
+ * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is
+ * required for a PagerAdapter to function properly.
+ *
+ * @param view Page View to check for association with object
+ * @param object Object to check for association with view
+ * @return true if view
is associated with the key object object
+ */
public abstract boolean isViewFromObject(View view, Object object);
- public abstract Parcelable saveState();
+ /**
+ * Save any instance state associated with this adapter and its pages that should be
+ * restored if the current UI state needs to be reconstructed.
+ *
+ * @return Saved state for this adapter
+ */
+ public Parcelable saveState() {
+ return null;
+ }
- public abstract void restoreState(Parcelable state, ClassLoader loader);
+ /**
+ * Restore any instance state associated with this adapter and its pages
+ * that was previously saved by {@link #saveState()}.
+ *
+ * @param state State previously saved by a call to {@link #saveState()}
+ * @param loader A ClassLoader that should be used to instantiate any restored objects
+ */
+ public void restoreState(Parcelable state, ClassLoader loader) {
+ }
/**
* Called when the host view is attempting to determine if an item's position
@@ -127,12 +273,27 @@ public abstract class PagerAdapter {
* and associated views should update.
*/
public void notifyDataSetChanged() {
- if (mObserver != null) {
- mObserver.onDataSetChanged();
- }
+ mObservable.notifyChanged();
+ }
+
+ void registerDataSetObserver(DataSetObserver observer) {
+ mObservable.registerObserver(observer);
}
- void setDataSetObserver(DataSetObserver observer) {
- mObserver = observer;
+ void unregisterDataSetObserver(DataSetObserver observer) {
+ mObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * This method may be called by the ViewPager to obtain a title string
+ * to describe the specified page. This method may return null
+ * indicating no title for this page. The default implementation returns
+ * null.
+ *
+ * @param position The position of the title requested
+ * @return A title for the requested page
+ */
+ public CharSequence getPageTitle(int position) {
+ return null;
}
}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/PagerTitleStrip.java b/actionbarsherlock/library/src/android/support/v4/view/PagerTitleStrip.java
new file mode 100644
index 000000000..b758c24d8
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/view/PagerTitleStrip.java
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+/**
+ * PagerTitleStrip is a non-interactive indicator of the current, next,
+ * and previous pages of a {@link ViewPager}. It is intended to be used as a
+ * child view of a ViewPager widget in your XML layout.
+ * Add it as a child of a ViewPager in your layout file and set its
+ * android:layout_gravity to TOP or BOTTOM to pin it to the top or bottom
+ * of the ViewPager. The title from each page is supplied by the method
+ * {@link PagerAdapter#getPageTitle(int)} in the adapter supplied to
+ * the ViewPager.
+ */
+public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor {
+ //private static final String TAG = "PagerTitleStrip";
+
+ ViewPager mPager;
+ private TextView mPrevText;
+ private TextView mCurrText;
+ private TextView mNextText;
+
+ private int mLastKnownCurrentPage = -1;
+ private float mLastKnownPositionOffset = -1;
+ private int mScaledTextSpacing;
+
+ private boolean mUpdatingText;
+ private boolean mUpdatingPositions;
+
+ private final PageListener mPageListener = new PageListener();
+
+ private static final int[] ATTRS = new int[] {
+ android.R.attr.textAppearance,
+ android.R.attr.textColor,
+ android.R.attr.textSize
+ };
+
+ private static final int SIDE_ALPHA = 0x99; // single-byte alpha, 0 = invisible, FF = opaque
+ private static final int TEXT_SPACING = 16; // dip
+
+ public PagerTitleStrip(Context context) {
+ this(context, null);
+ }
+
+ public PagerTitleStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ addView(mPrevText = new TextView(context));
+ addView(mCurrText = new TextView(context));
+ addView(mNextText = new TextView(context));
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+ final int textAppearance = a.getResourceId(0, 0);
+ if (textAppearance != 0) {
+ mPrevText.setTextAppearance(context, textAppearance);
+ mCurrText.setTextAppearance(context, textAppearance);
+ mNextText.setTextAppearance(context, textAppearance);
+ }
+ if (a.hasValue(1)) {
+ final int textColor = a.getColor(1, 0);
+ mPrevText.setTextColor(textColor);
+ mCurrText.setTextColor(textColor);
+ mNextText.setTextColor(textColor);
+ }
+ final int textSize = a.getDimensionPixelSize(2, 0);
+ if (textSize != 0) {
+ mPrevText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ mCurrText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ mNextText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
+ a.recycle();
+
+ final int defaultColor = mPrevText.getTextColors().getDefaultColor();
+ final int transparentColor = (SIDE_ALPHA << 24) | (defaultColor & 0xFFFFFF);
+ mPrevText.setTextColor(transparentColor);
+ mNextText.setTextColor(transparentColor);
+
+ mPrevText.setEllipsize(TruncateAt.END);
+ mCurrText.setEllipsize(TruncateAt.END);
+ mNextText.setEllipsize(TruncateAt.END);
+ mPrevText.setSingleLine();
+ mCurrText.setSingleLine();
+ mNextText.setSingleLine();
+
+ final float density = context.getResources().getDisplayMetrics().density;
+ mScaledTextSpacing = (int) (TEXT_SPACING * density);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final ViewParent parent = getParent();
+ if (!(parent instanceof ViewPager)) {
+ throw new IllegalStateException(
+ "PagerTitleStrip must be a direct child of a ViewPager.");
+ }
+
+ final ViewPager pager = (ViewPager) parent;
+ final PagerAdapter adapter = pager.getAdapter();
+
+ pager.setInternalPageChangeListener(mPageListener);
+ pager.setOnAdapterChangeListener(mPageListener);
+ mPager = pager;
+ updateAdapter(null, adapter);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ updateAdapter(mPager.getAdapter(), null);
+ mPager.setInternalPageChangeListener(null);
+ mPager.setOnAdapterChangeListener(null);
+ mPager = null;
+ }
+
+ void updateText(int currentItem, PagerAdapter adapter) {
+ final int itemCount = adapter != null ? adapter.getCount() : 0;
+ mUpdatingText = true;
+
+ CharSequence text = null;
+ if (currentItem >= 1 && adapter != null) {
+ text = adapter.getPageTitle(currentItem - 1);
+ }
+ mPrevText.setText(text);
+
+ mCurrText.setText(adapter != null ? adapter.getPageTitle(currentItem) : null);
+
+ text = null;
+ if (currentItem + 1 < itemCount && adapter != null) {
+ text = adapter.getPageTitle(currentItem + 1);
+ }
+ mNextText.setText(text);
+
+ // Measure everything
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+ final int childHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (width * 0.8f),
+ MeasureSpec.AT_MOST);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
+ mPrevText.measure(childWidthSpec, childHeightSpec);
+ mCurrText.measure(childWidthSpec, childHeightSpec);
+ mNextText.measure(childWidthSpec, childHeightSpec);
+
+ mLastKnownCurrentPage = currentItem;
+
+ if (!mUpdatingPositions) {
+ updateTextPositions(currentItem, mLastKnownPositionOffset);
+ }
+
+ mUpdatingText = false;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mUpdatingText) {
+ super.requestLayout();
+ }
+ }
+
+ void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
+ if (oldAdapter != null) {
+ oldAdapter.unregisterDataSetObserver(mPageListener);
+ }
+ if (newAdapter != null) {
+ newAdapter.registerDataSetObserver(mPageListener);
+ }
+ if (mPager != null) {
+ mLastKnownCurrentPage = -1;
+ mLastKnownPositionOffset = -1;
+ updateText(mPager.getCurrentItem(), newAdapter);
+ requestLayout();
+ }
+ }
+
+ void updateTextPositions(int position, float positionOffset) {
+ if (position != mLastKnownCurrentPage) {
+ updateText(position, mPager.getAdapter());
+ } else if (positionOffset == mLastKnownPositionOffset) {
+ return;
+ }
+
+ mUpdatingPositions = true;
+
+ final int prevWidth = mPrevText.getMeasuredWidth();
+ final int currWidth = mCurrText.getMeasuredWidth();
+ final int nextWidth = mNextText.getMeasuredWidth();
+ final int halfCurrWidth = currWidth / 2;
+
+ final int stripWidth = getWidth();
+ final int paddingLeft = getPaddingLeft();
+ final int paddingRight = getPaddingRight();
+ final int paddingTop = getPaddingTop();
+ final int textPaddedLeft = paddingLeft + halfCurrWidth;
+ final int textPaddedRight = paddingRight + halfCurrWidth;
+ final int contentWidth = stripWidth - textPaddedLeft - textPaddedRight;
+
+ float currOffset = positionOffset + 0.5f;
+ if (currOffset > 1.f) {
+ currOffset -= 1.f;
+ }
+ final int currCenter = stripWidth - textPaddedRight - (int) (contentWidth * currOffset);
+ final int currLeft = currCenter - currWidth / 2;
+ final int currRight = currLeft + currWidth;
+
+ mCurrText.layout(currLeft, paddingTop, currRight,
+ paddingTop + mCurrText.getMeasuredHeight());
+
+ final int prevLeft = Math.min(paddingLeft, currLeft - mScaledTextSpacing - prevWidth);
+ mPrevText.layout(prevLeft, paddingTop, prevLeft + prevWidth,
+ paddingTop + mPrevText.getMeasuredHeight());
+
+ final int nextLeft = Math.max(stripWidth - paddingRight - nextWidth,
+ currRight + mScaledTextSpacing);
+ mNextText.layout(nextLeft, paddingTop, nextLeft + nextWidth,
+ paddingTop + mNextText.getMeasuredHeight());
+
+ mLastKnownPositionOffset = positionOffset;
+ mUpdatingPositions = false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Must measure with an exact width");
+ }
+
+ int childHeight = heightSize;
+ int minHeight = 0;
+ int padding = 0;
+ final Drawable bg = getBackground();
+ if (bg != null) {
+ minHeight = bg.getIntrinsicHeight();
+ }
+ padding = getPaddingTop() + getPaddingBottom();
+ childHeight -= padding;
+
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * 0.8f),
+ MeasureSpec.AT_MOST);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode);
+
+ mPrevText.measure(childWidthSpec, childHeightSpec);
+ mCurrText.measure(childWidthSpec, childHeightSpec);
+ mNextText.measure(childWidthSpec, childHeightSpec);
+
+ if (heightMode == MeasureSpec.EXACTLY) {
+ setMeasuredDimension(widthSize, heightSize);
+ } else {
+ int textHeight = mCurrText.getMeasuredHeight();
+ setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding));
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (mPager != null) {
+ updateTextPositions(mPager.getCurrentItem(), 0.f);
+ }
+ }
+
+ private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener,
+ ViewPager.OnAdapterChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ if (positionOffset > 0.5f) {
+ // Consider ourselves to be on the next page when we're 50% of the way there.
+ position++;
+ }
+ updateTextPositions(position, positionOffset);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ // Only update the text here if we're not dragging or settling.
+ updateText(mPager.getCurrentItem(), mPager.getAdapter());
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+ }
+
+ @Override
+ public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
+ updateAdapter(oldAdapter, newAdapter);
+ }
+
+ @Override
+ public void onChanged() {
+ updateText(mPager.getCurrentItem(), mPager.getAdapter());
+ }
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/SubMenu.java b/actionbarsherlock/library/src/android/support/v4/view/SubMenu.java
index 953e193f7..0575b0fb7 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/SubMenu.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/SubMenu.java
@@ -3,7 +3,37 @@ package android.support.v4.view;
import android.graphics.drawable.Drawable;
import android.view.View;
-public interface SubMenu extends android.view.SubMenu {
+public interface SubMenu extends android.view.SubMenu, Menu {
+ @Override
+ MenuItem add(CharSequence title);
+
+ @Override
+ MenuItem add(int groupId, int itemId, int order, int titleRes);
+
+ @Override
+ MenuItem add(int titleRes);
+
+ @Override
+ MenuItem add(int groupId, int itemId, int order, CharSequence title);
+
+ @Override
+ SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title);
+
+ @Override
+ SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes);
+
+ @Override
+ SubMenu addSubMenu(CharSequence title);
+
+ @Override
+ SubMenu addSubMenu(int titleRes);
+
+ @Override
+ MenuItem findItem(int id);
+
+ @Override
+ MenuItem getItem(int index);
+
@Override
MenuItem getItem();
diff --git a/actionbarsherlock/library/src/android/support/v4/view/VelocityTrackerCompat.java b/actionbarsherlock/library/src/android/support/v4/view/VelocityTrackerCompat.java
index 8c8749061..d2a3e35c1 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/VelocityTrackerCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/VelocityTrackerCompat.java
@@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.VelocityTracker;
/**
- * Helper for accessing newer features in VelocityTracker.
+ * Helper for accessing features in {@link VelocityTracker}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class VelocityTrackerCompat {
/**
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ViewCompat.java b/actionbarsherlock/library/src/android/support/v4/view/ViewCompat.java
index 33d0cd2b7..d819371d7 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ViewCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ViewCompat.java
@@ -21,7 +21,8 @@ import android.view.View;
import android.view.accessibility.AccessibilityEvent;
/**
- * Helper for accessing newer features in View.
+ * Helper for accessing features in {@link View} introduced after API
+ * level 4 in a backwards compatible fashion.
*/
public class ViewCompat {
/**
@@ -129,34 +130,174 @@ public class ViewCompat {
}
}
+ /**
+ * Check if this view can be scrolled horizontally in a certain direction.
+ *
+ * @param v The View against which to invoke the method.
+ * @param direction Negative to check scrolling left, positive to check scrolling right.
+ * @return true if this view can be scrolled in the specified direction, false otherwise.
+ */
public static boolean canScrollHorizontally(View v, int direction) {
return IMPL.canScrollHorizontally(v, direction);
}
+ /**
+ * Check if this view can be scrolled vertically in a certain direction.
+ *
+ * @param v The View against which to invoke the method.
+ * @param direction Negative to check scrolling up, positive to check scrolling down.
+ * @return true if this view can be scrolled in the specified direction, false otherwise.
+ */
public static boolean canScrollVertically(View v, int direction) {
return IMPL.canScrollVertically(v, direction);
}
+ /**
+ * Returns the over-scroll mode for this view. The result will be
+ * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * (allow over-scrolling only if the view content is larger than the container),
+ * or {@link #OVER_SCROLL_NEVER}.
+ *
+ * @param v The View against which to invoke the method.
+ * @return This view's over-scroll mode.
+ */
public static int getOverScrollMode(View v) {
return IMPL.getOverScrollMode(v);
}
- public static void setOverScrollMode(View v, int mode) {
- IMPL.setOverScrollMode(v, mode);
+ /**
+ * Set the over-scroll mode for this view. Valid over-scroll modes are
+ * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * (allow over-scrolling only if the view content is larger than the container),
+ * or {@link #OVER_SCROLL_NEVER}.
+ *
+ * Setting the over-scroll mode of a view will have an effect only if the
+ * view is capable of scrolling.
+ *
+ * @param v The View against which to invoke the method.
+ * @param overScrollMode The new over-scroll mode for this view.
+ */
+ public static void setOverScrollMode(View v, int overScrollMode) {
+ IMPL.setOverScrollMode(v, overScrollMode);
}
+ /**
+ * Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * giving a chance to this View to populate the accessibility event with its
+ * text content. While this method is free to modify event
+ * attributes other than text content, doing so should normally be performed in
+ * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}.
+ *
+ * Example: Adding formatted date string to an accessibility event in addition
+ * to the text added by the super implementation:
+ *
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ * super.onPopulateAccessibilityEvent(event);
+ * final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
+ * String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ * mCurrentDate.getTimeInMillis(), flags);
+ * event.getText().add(selectedDateUtterance);
+ * }
+ *
+ * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
+ * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
+ * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
+ * AccessibilityEvent)}
+ * is responsible for handling this call.
+ *
+ * Note: Always call the super implementation before adding
+ * information to the event, in case the default implementation has basic information to add.
+ *
+ *
+ * @param v The View against which to invoke the method.
+ * @param event The accessibility event which to populate.
+ *
+ * @see View#sendAccessibilityEvent(int)
+ * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
IMPL.onPopulateAccessibilityEvent(v, event);
}
+ /**
+ * Initializes an {@link AccessibilityEvent} with information about
+ * this View which is the event source. In other words, the source of
+ * an accessibility event is the view whose state change triggered firing
+ * the event.
+ *
+ * Example: Setting the password property of an event in addition
+ * to properties set by the super implementation:
+ *
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ * super.onInitializeAccessibilityEvent(event);
+ * event.setPassword(true);
+ * }
+ *
+ * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
+ * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
+ * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
+ * AccessibilityEvent)}
+ * is responsible for handling this call.
+ *
+ * Note: Always call the super implementation before adding
+ * information to the event, in case the default implementation has basic information to add.
+ *
+ *
+ * @param v The View against which to invoke the method.
+ * @param event The event to initialize.
+ *
+ * @see View#sendAccessibilityEvent(int)
+ * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
IMPL.onInitializeAccessibilityEvent(v, event);
}
+ /**
+ * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+ * about this view. The base implementation sets:
+ *
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},
+ *
+ *
+ * Subclasses should override this method, call the super implementation,
+ * and set additional attributes.
+ *
+ *
+ * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
+ * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
+ * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
+ * android.view.accessibility.AccessibilityNodeInfo)}
+ * is responsible for handling this call.
+ *
+ *
+ * @param v The View against which to invoke the method.
+ * @param info The instance to initialize.
+ */
public static void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) {
IMPL.onInitializeAccessibilityNodeInfo(v, info);
}
+ /**
+ * Sets a delegate for implementing accessibility support via compositon as
+ * opposed to inheritance. The delegate's primary use is for implementing
+ * backwards compatible widgets. For more details see
+ * {@link android.view.View.AccessibilityDelegate}.
+ *
+ * @param v The View against which to invoke the method.
+ * @param delegate The delegate instance.
+ *
+ * @see android.view.View.AccessibilityDelegate
+ */
public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
IMPL.setAccessibilityDelegate(v, delegate);
}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ViewCompatGingerbread.java b/actionbarsherlock/library/src/android/support/v4/view/ViewCompatGingerbread.java
index e063778d3..f410a6941 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ViewCompatGingerbread.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ViewCompatGingerbread.java
@@ -18,7 +18,7 @@ package android.support.v4.view;
import android.view.View;
-public class ViewCompatGingerbread {
+class ViewCompatGingerbread {
public static int getOverScrollMode(View v) {
return v.getOverScrollMode();
}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ViewConfigurationCompat.java b/actionbarsherlock/library/src/android/support/v4/view/ViewConfigurationCompat.java
index 144466212..b270fc328 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ViewConfigurationCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ViewConfigurationCompat.java
@@ -19,7 +19,8 @@ package android.support.v4.view;
import android.view.ViewConfiguration;
/**
- * Helper for accessing newer features in ViewConfiguration.
+ * Helper for accessing features in {@link ViewConfiguration}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class ViewConfigurationCompat {
/**
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ViewGroupCompat.java b/actionbarsherlock/library/src/android/support/v4/view/ViewGroupCompat.java
index 162554f32..11fa8c43d 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ViewGroupCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ViewGroupCompat.java
@@ -22,7 +22,8 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
- * Helper for accessing newer features in ViewGroup.
+ * Helper for accessing features in {@link ViewGroup}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class ViewGroupCompat {
diff --git a/actionbarsherlock/library/src/android/support/v4/view/ViewPager.java b/actionbarsherlock/library/src/android/support/v4/view/ViewPager.java
index 9aa7b0eb1..aba1ac96c 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/ViewPager.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/ViewPager.java
@@ -17,9 +17,12 @@
package android.support.v4.view;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -29,6 +32,7 @@ import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
@@ -54,6 +58,24 @@ import java.util.Comparator;
* development. The API will likely change in later updates of
* the compatibility library, requiring changes to the source code
* of apps when they are compiled against the newer version.
+ *
+ * ViewPager is most often used in conjunction with {@link android.app.Fragment},
+ * which is a convenient way to supply and manage the lifecycle of each page.
+ * There are standard adapters implemented for using fragments with the ViewPager,
+ * which cover the most common use cases. These are
+ * {@link android.support.v4.app.FragmentPagerAdapter},
+ * {@link android.support.v4.app.FragmentStatePagerAdapter},
+ * {@link android.support.v13.app.FragmentPagerAdapter}, and
+ * {@link android.support.v13.app.FragmentStatePagerAdapter}; each of these
+ * classes have simple code showing how to build a full user interface
+ * with them.
+ *
+ *
Here is a more complicated example of ViewPager, using it in conjuction
+ * with {@link android.app.ActionBar} tabs. You can find other examples of using
+ * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
+ *
+ * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
+ * complete}
*/
public class ViewPager extends ViewGroup {
private static final String TAG = "ViewPager";
@@ -63,6 +85,11 @@ public class ViewPager extends ViewGroup {
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private static final int MAX_SETTLE_DURATION = 600; // ms
+ private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
+
+ private static final int[] LAYOUT_ATTRS = new int[] {
+ android.R.attr.layout_gravity
+ };
static class ItemInfo {
Object object;
@@ -78,10 +105,8 @@ public class ViewPager extends ViewGroup {
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
- // _o(t) = t * t * ((tension + 1) * t + tension)
- // o(t) = _o(t - 1) + 1
t -= 1.0f;
- return t * t * t + 1.0f;
+ return t * t * t * t * t + 1.0f;
}
};
@@ -93,10 +118,12 @@ public class ViewPager extends ViewGroup {
private Parcelable mRestoredAdapterState = null;
private ClassLoader mRestoredClassLoader = null;
private Scroller mScroller;
- private PagerAdapter.DataSetObserver mObserver;
+ private PagerObserver mObserver;
private int mPageMargin;
private Drawable mMarginDrawable;
+ private int mTopPageBounds;
+ private int mBottomPageBounds;
private int mChildWidthMeasureSpec;
private int mChildHeightMeasureSpec;
@@ -134,8 +161,7 @@ public class ViewPager extends ViewGroup {
private VelocityTracker mVelocityTracker;
private int mMinimumVelocity;
private int mMaximumVelocity;
- private float mBaseLineFlingVelocity;
- private float mFlingVelocityInfluence;
+ private int mFlingDistance;
private boolean mFakeDragging;
private long mFakeDragBeginTime;
@@ -144,8 +170,12 @@ public class ViewPager extends ViewGroup {
private EdgeEffectCompat mRightEdge;
private boolean mFirstLayout = true;
+ private boolean mCalledSuper;
+ private int mDecorChildCount;
private OnPageChangeListener mOnPageChangeListener;
+ private OnPageChangeListener mInternalPageChangeListener;
+ private OnAdapterChangeListener mAdapterChangeListener;
/**
* Indicates that the pager is in an idle, settled state. The current page
@@ -224,6 +254,19 @@ public class ViewPager extends ViewGroup {
}
}
+ /**
+ * Used internally to monitor when adapters are switched.
+ */
+ interface OnAdapterChangeListener {
+ public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
+ }
+
+ /**
+ * Used internally to tag special types of child views that should be added as
+ * pager decorations by default.
+ */
+ interface Decor {}
+
public ViewPager(Context context) {
super(context);
initViewPager();
@@ -247,9 +290,8 @@ public class ViewPager extends ViewGroup {
mLeftEdge = new EdgeEffectCompat(context);
mRightEdge = new EdgeEffectCompat(context);
- float density = context.getResources().getDisplayMetrics().density;
- mBaseLineFlingVelocity = 2500.0f * density;
- mFlingVelocityInfluence = 0.4f;
+ final float density = context.getResources().getDisplayMetrics().density;
+ mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
}
private void setScrollState(int newState) {
@@ -263,9 +305,14 @@ public class ViewPager extends ViewGroup {
}
}
+ /**
+ * Set a PagerAdapter that will supply views for this pager as needed.
+ *
+ * @param adapter Adapter to use
+ */
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
- mAdapter.setDataSetObserver(null);
+ mAdapter.unregisterDataSetObserver(mObserver);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
@@ -273,18 +320,19 @@ public class ViewPager extends ViewGroup {
}
mAdapter.finishUpdate(this);
mItems.clear();
- removeAllViews();
+ removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
+ final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
if (mAdapter != null) {
if (mObserver == null) {
- mObserver = new DataSetObserver();
+ mObserver = new PagerObserver();
}
- mAdapter.setDataSetObserver(mObserver);
+ mAdapter.registerDataSetObserver(mObserver);
mPopulatePending = false;
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
@@ -296,12 +344,36 @@ public class ViewPager extends ViewGroup {
populate();
}
}
+
+ if (mAdapterChangeListener != null && oldAdapter != adapter) {
+ mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
+ }
}
+ private void removeNonDecorViews() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isDecor) {
+ removeViewAt(i);
+ i--;
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current adapter supplying pages.
+ *
+ * @return The currently registered PagerAdapter
+ */
public PagerAdapter getAdapter() {
return mAdapter;
}
+ void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
+ mAdapterChangeListener = listener;
+ }
+
/**
* Set the currently selected page. If the ViewPager has already been through its first
* layout there will be a smooth animated transition between the current item and the
@@ -365,19 +437,43 @@ public class ViewPager extends ViewGroup {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
+ if (dispatchSelected && mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageSelected(item);
+ }
} else {
if (dispatchSelected && mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(item);
}
+ if (dispatchSelected && mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageSelected(item);
+ }
completeScroll();
scrollTo(destX, 0);
}
}
+ /**
+ * Set a listener that will be invoked whenever the page changes or is incrementally
+ * scrolled. See {@link OnPageChangeListener}.
+ *
+ * @param listener Listener to set
+ */
public void setOnPageChangeListener(OnPageChangeListener listener) {
mOnPageChangeListener = listener;
}
+ /**
+ * Set a separate OnPageChangeListener for internal use by the support library.
+ *
+ * @param listener Listener to set
+ * @return The old listener that was set, if any.
+ */
+ OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
+ OnPageChangeListener oldListener = mInternalPageChangeListener;
+ mInternalPageChangeListener = listener;
+ return oldListener;
+ }
+
/**
* Returns the number of pages that will be retained to either side of the
* current page in the view hierarchy in an idle state. Defaults to 1.
@@ -527,14 +623,19 @@ public class ViewPager extends ViewGroup {
mScrolling = true;
setScrollState(SCROLL_STATE_SETTLING);
- final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
- int duration = (int) (pageDelta * 100);
+ final int width = getWidth();
+ final int halfWidth = width / 2;
+ final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
+ final float distance = halfWidth + halfWidth *
+ distanceInfluenceForSnapDuration(distanceRatio);
+ int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
- duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
- duration += 100;
+ final float pageDelta = (float) Math.abs(dx) / (width + mPageMargin);
+ duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
@@ -559,6 +660,7 @@ public class ViewPager extends ViewGroup {
boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
int newCurrItem = -1;
+ boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
@@ -570,6 +672,12 @@ public class ViewPager extends ViewGroup {
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
+
+ if (!isUpdating) {
+ mAdapter.startUpdate(this);
+ isUpdating = true;
+ }
+
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
@@ -591,6 +699,10 @@ public class ViewPager extends ViewGroup {
}
}
+ if (isUpdating) {
+ mAdapter.finishUpdate(this);
+ }
+
Collections.sort(mItems, COMPARATOR);
if (newCurrItem >= 0) {
@@ -708,6 +820,12 @@ public class ViewPager extends ViewGroup {
}
}
+ /**
+ * This is the persistent state that is saved by ViewPager. Only needed
+ * if you are creating a sublass of ViewPager that must save its own
+ * state, in which case it should implement a subclass of this which
+ * contains that state.
+ */
public static class SavedState extends BaseSavedState {
int position;
Parcelable adapterState;
@@ -786,8 +904,16 @@ public class ViewPager extends ViewGroup {
}
@Override
- public void addView(View child, int index, LayoutParams params) {
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (!checkLayoutParams(params)) {
+ params = generateLayoutParams(params);
+ }
+ final LayoutParams lp = (LayoutParams) params;
+ lp.isDecor |= child instanceof Decor;
if (mInLayout) {
+ if (lp != null && lp.isDecor) {
+ throw new IllegalStateException("Cannot add pager decor view during layout");
+ }
addViewInLayout(child, index, params);
child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
} else {
@@ -841,24 +967,67 @@ public class ViewPager extends ViewGroup {
getDefaultSize(0, heightMeasureSpec));
// Children are just made to fill our space.
- mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
- getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
- mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
- getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
+ int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+ int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+
+ /*
+ * Make sure all children have been properly measured. Decor views first.
+ * Right now we cheat and make this less complicated by assuming decor
+ * views won't intersect. We will pin to edges based on gravity.
+ */
+ int size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp != null && lp.isDecor) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ Log.d(TAG, "gravity: " + lp.gravity + " hgrav: " + hgrav + " vgrav: " + vgrav);
+ int widthMode = MeasureSpec.AT_MOST;
+ int heightMode = MeasureSpec.AT_MOST;
+ boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
+ boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
+
+ if (consumeVertical) {
+ widthMode = MeasureSpec.EXACTLY;
+ } else if (consumeHorizontal) {
+ heightMode = MeasureSpec.EXACTLY;
+ }
+
+ final int widthSpec = MeasureSpec.makeMeasureSpec(childWidthSize, widthMode);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, heightMode);
+ child.measure(widthSpec, heightSpec);
+
+ if (consumeVertical) {
+ childHeightSize -= child.getMeasuredHeight();
+ } else if (consumeHorizontal) {
+ childWidthSize -= child.getMeasuredWidth();
+ }
+ }
+ }
+ }
+
+ mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
+ mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
- // Make sure all children have been properly measured.
- final int size = getChildCount();
+ // Page views next.
+ size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
- + ": " + mChildWidthMeasureSpec);
- child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+ + ": " + mChildWidthMeasureSpec);
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp == null || !lp.isDecor) {
+ child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+ }
}
}
}
@@ -903,23 +1072,81 @@ public class ViewPager extends ViewGroup {
mInLayout = false;
final int count = getChildCount();
- final int width = r-l;
+ int width = r - l;
+ int height = b - t;
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+ final int scrollX = getScrollX();
+
+ int decorCount = 0;
for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- ItemInfo ii;
- if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
- int loff = (width + mPageMargin) * ii.position;
- int childLeft = getPaddingLeft() + loff;
- int childTop = getPaddingTop();
- if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
- + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
- + "x" + child.getMeasuredHeight());
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(),
- childTop + child.getMeasuredHeight());
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ ItemInfo ii;
+ int childLeft = 0;
+ int childTop = 0;
+ if (lp.isDecor) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ switch (hgrav) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getMeasuredWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+ paddingLeft);
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ switch (vgrav) {
+ default:
+ childTop = paddingTop;
+ break;
+ case Gravity.TOP:
+ childTop = paddingTop;
+ paddingTop += child.getMeasuredHeight();
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = Math.max((height - child.getMeasuredHeight()) / 2,
+ paddingTop);
+ break;
+ case Gravity.BOTTOM:
+ childTop = height - paddingBottom - child.getMeasuredHeight();
+ paddingBottom += child.getMeasuredHeight();
+ break;
+ }
+ childLeft += scrollX;
+ decorCount++;
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight());
+ } else if ((ii = infoForChild(child)) != null) {
+ int loff = (width + mPageMargin) * ii.position;
+ childLeft = paddingLeft + loff;
+ childTop = paddingTop;
+ if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ + "x" + child.getMeasuredHeight());
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight());
+ }
}
}
+ mTopPageBounds = paddingTop;
+ mBottomPageBounds = height - paddingBottom;
+ mDecorChildCount = decorCount;
mFirstLayout = false;
}
@@ -936,14 +1163,7 @@ public class ViewPager extends ViewGroup {
if (oldX != x || oldY != y) {
scrollTo(x, y);
- }
-
- if (mOnPageChangeListener != null) {
- final int widthWithMargin = getWidth() + mPageMargin;
- final int position = x / widthWithMargin;
- final int offsetPixels = x % widthWithMargin;
- final float offset = (float) offsetPixels / widthWithMargin;
- mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+ pageScrolled(x);
}
// Keep on drawing until the animation has finished.
@@ -956,6 +1176,82 @@ public class ViewPager extends ViewGroup {
completeScroll();
}
+ private void pageScrolled(int xpos) {
+ final int widthWithMargin = getWidth() + mPageMargin;
+ final int position = xpos / widthWithMargin;
+ final int offsetPixels = xpos % widthWithMargin;
+ final float offset = (float) offsetPixels / widthWithMargin;
+
+ mCalledSuper = false;
+ onPageScrolled(position, offset, offsetPixels);
+ if (!mCalledSuper) {
+ throw new IllegalStateException(
+ "onPageScrolled did not call superclass implementation");
+ }
+ }
+
+ /**
+ * This method will be invoked when the current page is scrolled, either as part
+ * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+ * If you override this method you must call through to the superclass implementation
+ * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
+ * returns.
+ *
+ * @param position Position index of the first page currently being displayed.
+ * Page position+1 will be visible if positionOffset is nonzero.
+ * @param offset Value from [0, 1) indicating the offset from the page at position.
+ * @param offsetPixels Value in pixels indicating the offset from position.
+ */
+ protected void onPageScrolled(int position, float offset, int offsetPixels) {
+ // Offset any decor views if needed - keep them on-screen at all times.
+ if (mDecorChildCount > 0) {
+ final int scrollX = getScrollX();
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+ final int width = getWidth();
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isDecor) continue;
+
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int childLeft = 0;
+ switch (hgrav) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+ paddingLeft);
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ childLeft += scrollX;
+
+ final int childOffset = childLeft - child.getLeft();
+ if (childOffset != 0) {
+ child.offsetLeftAndRight(childOffset);
+ }
+ }
+ }
+
+ if (mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+ }
+ if (mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+ }
+ mCalledSuper = true;
+ }
+
private void completeScroll() {
boolean needPopulate = mScrolling;
if (needPopulate) {
@@ -1002,6 +1298,10 @@ public class ViewPager extends ViewGroup {
mIsBeingDragged = false;
mIsUnableToDrag = false;
mActivePointerId = INVALID_POINTER;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
return false;
}
@@ -1041,9 +1341,6 @@ public class ViewPager extends ViewGroup {
final float xDiff = Math.abs(dx);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
- //UNUSED: final int scrollX = getScrollX();
- //UNUSED: final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
- // scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
@@ -1102,10 +1399,19 @@ public class ViewPager extends ViewGroup {
break;
}
+ if (!mIsBeingDragged) {
+ // Track the velocity as long as we aren't dragging.
+ // Once we start a real drag we will track in onTouchEvent.
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
/*
- * The only time we want to intercept motion events is if we are in the
- * drag mode.
- */
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
return mIsBeingDragged;
}
@@ -1198,13 +1504,7 @@ public class ViewPager extends ViewGroup {
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
- if (mOnPageChangeListener != null) {
- final int position = (int) scrollX / widthWithMargin;
- final int positionOffsetPixels = (int) scrollX % widthWithMargin;
- final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
- mOnPageChangeListener.onPageScrolled(position, positionOffset,
- positionOffsetPixels);
- }
+ pageScrolled((int) scrollX);
}
break;
case MotionEvent.ACTION_UP:
@@ -1217,7 +1517,13 @@ public class ViewPager extends ViewGroup {
final int widthWithMargin = getWidth() + mPageMargin;
final int scrollX = getScrollX();
final int currentPage = scrollX / widthWithMargin;
- int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
+ final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin;
+ final int activePointerIndex =
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);
+ final int totalDelta = (int) (x - mInitialMotionX);
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+ totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
mActivePointerId = INVALID_POINTER;
@@ -1252,6 +1558,17 @@ public class ViewPager extends ViewGroup {
return true;
}
+ private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
+ int targetPage;
+ if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
+ targetPage = velocity > 0 ? currentPage : currentPage + 1;
+ } else {
+ targetPage = (int) (currentPage + pageOffset + 0.5f);
+ }
+
+ return targetPage;
+ }
+
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -1307,7 +1624,8 @@ public class ViewPager extends ViewGroup {
if (offset != 0) {
// Pages fit completely when settled; we only need to draw when in between
final int left = scrollX - offset + width;
- mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
+ mMarginDrawable.setBounds(left, mTopPageBounds, left + mPageMargin,
+ mBottomPageBounds);
mMarginDrawable.draw(canvas);
}
}
@@ -1366,16 +1684,13 @@ public class ViewPager extends ViewGroup {
int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
- if ((Math.abs(initialVelocity) > mMinimumVelocity)
- || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
- if (mLastMotionX > mInitialMotionX) {
- setCurrentItemInternal(mCurItem-1, true, true);
- } else {
- setCurrentItemInternal(mCurItem+1, true, true);
- }
- } else {
- setCurrentItemInternal(mCurItem, true, true);
- }
+ final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
+ final int scrollX = getScrollX();
+ final int widthWithMargin = getWidth() + mPageMargin;
+ final int currentPage = scrollX / widthWithMargin;
+ final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin;
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta);
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);
endDrag();
mFakeDragging = false;
@@ -1409,13 +1724,7 @@ public class ViewPager extends ViewGroup {
// Don't lose the rounded component
mLastMotionX += scrollX - (int) scrollX;
scrollTo((int) scrollX, getScrollY());
- if (mOnPageChangeListener != null) {
- final int position = (int) scrollX / widthWithMargin;
- final int positionOffsetPixels = (int) scrollX % widthWithMargin;
- final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
- mOnPageChangeListener.onPageScrolled(position, positionOffset,
- positionOffsetPixels);
- }
+ pageScrolled((int) scrollX);
// Synthesize an event for the VelocityTracker.
final long time = SystemClock.uptimeMillis();
@@ -1537,10 +1846,14 @@ public class ViewPager extends ViewGroup {
handled = arrowScroll(FOCUS_RIGHT);
break;
case KeyEvent.KEYCODE_TAB:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = arrowScroll(FOCUS_FORWARD);
- } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
- handled = arrowScroll(FOCUS_BACKWARD);
+ if (Build.VERSION.SDK_INT >= 11) {
+ // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
+ // before Android 3.0. Ignore the tab key on those devices.
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ handled = arrowScroll(FOCUS_FORWARD);
+ } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
+ handled = arrowScroll(FOCUS_BACKWARD);
+ }
}
break;
}
@@ -1721,10 +2034,64 @@ public class ViewPager extends ViewGroup {
return false;
}
- private class DataSetObserver implements PagerAdapter.DataSetObserver {
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && super.checkLayoutParams(p);
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ private class PagerObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ dataSetChanged();
+ }
@Override
- public void onDataSetChanged() {
+ public void onInvalidated() {
dataSetChanged();
}
}
+
+ /**
+ * Layout parameters that should be supplied for views added to a
+ * ViewPager.
+ */
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+ /**
+ * true if this view is a decoration on the pager itself and not
+ * a view supplied by the adapter.
+ */
+ public boolean isDecor;
+
+ /**
+ * Where to position the view page within the overall ViewPager
+ * container; constants are defined in {@link android.view.Gravity}.
+ */
+ public int gravity;
+
+ public LayoutParams() {
+ super(FILL_PARENT, FILL_PARENT);
+ }
+
+ public LayoutParams(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+ gravity = a.getInteger(0, Gravity.NO_GRAVITY);
+ a.recycle();
+ }
+ }
}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/Window.java b/actionbarsherlock/library/src/android/support/v4/view/Window.java
index 531189574..479dff984 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/Window.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/Window.java
@@ -44,13 +44,6 @@ public abstract class Window extends android.view.Window {
*/
public static final long FEATURE_ACTION_BAR = android.view.Window.FEATURE_ACTION_BAR;
- /**
- * Enable the pre-3.0 action bar implementation to honor the 'withText'
- * attribute on menu items regardless of display resolution and/or density.
- */
- //Hopefully the native Window.FEATURE_XXX numbering won't get this high anytime soon.
- public static final long FEATURE_ACTION_BAR_ITEM_TEXT = 31;
-
/**
* Flag for requesting an Action Bar that overlays window content. Normally
* an Action Bar will sit in the space above window content, but if this
diff --git a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java
index 40c3ff90e..702e14623 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java
@@ -20,7 +20,8 @@ package android.support.v4.view.accessibility;
import android.view.accessibility.AccessibilityEvent;
/**
- * Helper for accessing newer features in AccessibilityEvent.
+ * Helper for accessing features in {@link AccessibilityEvent}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityEventCompat {
diff --git a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityManagerCompat.java b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
index 2ce8c4cfd..d6e06a355 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
@@ -18,13 +18,15 @@ package android.support.v4.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
//import android.os.Build;
-import android.view.accessibility.AccessibilityManager;
//import android.support.v4.view.accessibility.AccessibilityManagerCompatIcs.AccessibilityStateChangeListenerBridge;
+import android.view.accessibility.AccessibilityManager;
+import java.util.Collections;
import java.util.List;
/**
- * Helper for accessing newer features in AccessibilityManager.
+ * Helper for accessing features in {@link AccessibilityManager}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityManagerCompat {
@@ -60,12 +62,12 @@ public class AccessibilityManagerCompat {
public List getEnabledAccessibilityServiceList(
AccessibilityManager manager, int feedbackTypeFlags) {
- return null;
+ return Collections.emptyList();
}
public List getInstalledAccessibilityServiceList(
AccessibilityManager manager) {
- return null;
+ return Collections.emptyList();
}
public boolean isTouchExplorationEnabled(AccessibilityManager manager) {
diff --git a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 229c0c5b9..814741421 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -21,10 +21,12 @@ import android.graphics.Rect;
import android.view.View;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
- * Helper for accessing AccessibilityNodeInfo from newer platform versions.
+ * Helper for accessing {@link android.view.accessibility.AccessibilityNodeInfo}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityNodeInfoCompat {
@@ -100,7 +102,7 @@ public class AccessibilityNodeInfoCompat {
}
public List findAccessibilityNodeInfosByText(Object info, String text) {
- return null;
+ return Collections.emptyList();
}
public int getActions(Object info) {
diff --git a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
index 5967b8b85..623c9c844 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
@@ -20,10 +20,12 @@ package android.support.v4.view.accessibility;
import android.os.Parcelable;
import android.view.View;
+import java.util.Collections;
import java.util.List;
/**
- * Helper for accessing AccessibilityRecord from newer platform versions.
+ * Helper for accessing {@link android.view.accessibility.AccessibilityRecord}
+ * introduced after API level 4 in a backwards compatible fashion.
*/
public class AccessibilityRecordCompat {
@@ -141,7 +143,7 @@ public class AccessibilityRecordCompat {
}
public List getText(Object record) {
- return null;
+ return Collections.emptyList();
}
public int getToIndex(Object record) {
diff --git a/actionbarsherlock/library/src/android/support/v4/widget/CursorFilter.java b/actionbarsherlock/library/src/android/support/v4/widget/CursorFilter.java
index 69b87be27..bb05879f8 100644
--- a/actionbarsherlock/library/src/android/support/v4/widget/CursorFilter.java
+++ b/actionbarsherlock/library/src/android/support/v4/widget/CursorFilter.java
@@ -20,10 +20,10 @@ import android.database.Cursor;
import android.widget.Filter;
/**
- * The CursorFilter delegates most of the work to the CursorAdapter.
- * Subclasses should override these delegate methods to run the queries
- * and convert the results into String that can be used by auto-completion
- * widgets.
+ * The CursorFilter delegates most of the work to the
+ * {@link android.widget.CursorAdapter}. Subclasses should override these
+ * delegate methods to run the queries and convert the results into String
+ * that can be used by auto-completion widgets.
*/
class CursorFilter extends Filter {
diff --git a/actionbarsherlock/library/src/android/support/v4/widget/EdgeEffectCompat.java b/actionbarsherlock/library/src/android/support/v4/widget/EdgeEffectCompat.java
index 42eb1c1c7..9ebafb738 100644
--- a/actionbarsherlock/library/src/android/support/v4/widget/EdgeEffectCompat.java
+++ b/actionbarsherlock/library/src/android/support/v4/widget/EdgeEffectCompat.java
@@ -19,7 +19,8 @@ import android.content.Context;
import android.graphics.Canvas;
/**
- * Helper for accessing EdgeEffects from newer platform versions.
+ * Helper for accessing {@link android.widget.EdgeEffect} introduced after
+ * API level 4 in a backwards compatible fashion.
*
* This class is used to access {@link android.widget.EdgeEffect} on platform versions
* that support it. When running on older platforms it will result in no-ops. It should
diff --git a/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompat.java b/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompat.java
new file mode 100644
index 000000000..29568397b
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompat.java
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.view.View;
+
+/**
+ * Helper for accessing features in {@link android.widget.SearchView}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class SearchViewCompat {
+
+ interface SearchViewCompatImpl {
+ View newSearchView(Context context);
+ Object newOnQueryTextListener(OnQueryTextListenerCompat listener);
+ void setOnQueryTextListener(Object searchView, Object listener);
+ }
+
+ static class SearchViewCompatStubImpl implements SearchViewCompatImpl {
+
+ @Override
+ public View newSearchView(Context context) {
+ return null;
+ }
+
+ @Override
+ public Object newOnQueryTextListener(OnQueryTextListenerCompat listener) {
+ return null;
+ }
+
+ @Override
+ public void setOnQueryTextListener(Object searchView, Object listener) {
+
+ }
+ }
+
+ static class SearchViewCompatHoneycombImpl extends SearchViewCompatStubImpl {
+
+ @Override
+ public View newSearchView(Context context) {
+ return SearchViewCompatHoneycomb.newSearchView(context);
+ }
+
+ @Override
+ public Object newOnQueryTextListener(final OnQueryTextListenerCompat listener) {
+ return SearchViewCompatHoneycomb.newOnQueryTextListener(
+ new SearchViewCompatHoneycomb.OnQueryTextListenerCompatBridge() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return listener.onQueryTextSubmit(query);
+ }
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ return listener.onQueryTextChange(newText);
+ }
+ });
+ }
+
+ @Override
+ public void setOnQueryTextListener(Object searchView, Object listener) {
+ SearchViewCompatHoneycomb.setOnQueryTextListener(searchView, listener);
+ }
+ }
+
+ private static final SearchViewCompatImpl IMPL;
+
+ static {
+ if (Build.VERSION.SDK_INT >= 11) { // Honeycomb
+ IMPL = new SearchViewCompatHoneycombImpl();
+ } else {
+ IMPL = new SearchViewCompatStubImpl();
+ }
+ }
+
+ private SearchViewCompat(Context context) {
+ /* Hide constructor */
+ }
+
+ /**
+ * Creates a new SearchView.
+ *
+ * @param context The Context the view is running in.
+ * @return A SearchView instance if the class is present on the current
+ * platform, null otherwise.
+ */
+ public static View newSearchView(Context context) {
+ return IMPL.newSearchView(context);
+ }
+
+ /**
+ * Sets a listener for user actions within the SearchView.
+ *
+ * @param searchView The SearchView in which to register the listener.
+ * @param listener the listener object that receives callbacks when the user performs
+ * actions in the SearchView such as clicking on buttons or typing a query.
+ */
+ public static void setOnQueryTextListener(View searchView, OnQueryTextListenerCompat listener) {
+ IMPL.setOnQueryTextListener(searchView, listener.mListener);
+ }
+
+ /**
+ * Callbacks for changes to the query text.
+ */
+ public static abstract class OnQueryTextListenerCompat {
+ final Object mListener;
+
+ public OnQueryTextListenerCompat() {
+ mListener = IMPL.newOnQueryTextListener(this);
+ }
+
+ /**
+ * Called when the user submits the query. This could be due to a key press on the
+ * keyboard or due to pressing a submit button.
+ * The listener can override the standard behavior by returning true
+ * to indicate that it has handled the submit request. Otherwise return false to
+ * let the SearchView handle the submission by launching any associated intent.
+ *
+ * @param query the query text that is to be submitted
+ *
+ * @return true if the query has been handled by the listener, false to let the
+ * SearchView perform the default action.
+ */
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ /**
+ * Called when the query text is changed by the user.
+ *
+ * @param newText the new content of the query text field.
+ *
+ * @return false if the SearchView should perform the default action of showing any
+ * suggestions if available, true if the action was handled by the listener.
+ */
+ public boolean onQueryTextChange(String newText) {
+ return false;
+ }
+ }
+}
diff --git a/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompatHoneycomb.java b/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompatHoneycomb.java
new file mode 100644
index 000000000..c03556fcc
--- /dev/null
+++ b/actionbarsherlock/library/src/android/support/v4/widget/SearchViewCompatHoneycomb.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
+
+/**
+ * Implementation of SearchView compatibility that can call Honeycomb APIs.
+ */
+class SearchViewCompatHoneycomb {
+
+ interface OnQueryTextListenerCompatBridge {
+ public boolean onQueryTextSubmit(String query);
+ public boolean onQueryTextChange(String newText);
+ }
+
+ public static View newSearchView(Context context) {
+ return new SearchView(context);
+ }
+
+ public static Object newOnQueryTextListener(final OnQueryTextListenerCompatBridge listener) {
+ return new OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return listener.onQueryTextSubmit(query);
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ return listener.onQueryTextChange(newText);
+ }
+ };
+ }
+
+ public static void setOnQueryTextListener(Object searchView, Object listener) {
+ ((SearchView) searchView).setOnQueryTextListener((OnQueryTextListener) listener);
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarImpl.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
index 70cf23bc5..cca063c07 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
@@ -21,25 +21,25 @@ import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBar;
import android.support.v4.view.ActionMode;
-import android.support.v4.view.MenuItem;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.SpinnerAdapter;
import com.actionbarsherlock.R;
-import com.actionbarsherlock.internal.view.menu.ActionMenuItemView;
import com.actionbarsherlock.internal.view.menu.MenuBuilder;
-import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
+import com.actionbarsherlock.internal.view.menu.MenuPresenter;
import com.actionbarsherlock.internal.widget.ActionBarContainer;
import com.actionbarsherlock.internal.widget.ActionBarView;
public final class ActionBarImpl extends ActionBar {
- private final Activity mActivity;
+ private Context mContext;
/** Action bar container. */
private ActionBarContainer mContainerView;
@@ -56,7 +56,9 @@ public final class ActionBarImpl extends ActionBar {
public ActionBarImpl(Activity activity) {
- mActivity = activity;
+ Window window = activity.getWindow();
+ View decor = window.getDecorView();
+ init(decor);
}
@@ -64,91 +66,21 @@ public final class ActionBarImpl extends ActionBar {
// ACTION BAR SHERLOCK SUPPORT
// ------------------------------------------------------------------------
- @Override
- protected ActionBar getPublicInstance() {
- return this;
- }
-
- public void init() {
- mActionView = (ActionBarView)mActivity.findViewById(R.id.abs__action_bar);
- mContainerView = (ActionBarContainer)mActivity.findViewById(R.id.abs__action_bar_container);
+ private void init(View decor) {
+ mContext = decor.getContext();
+ mActionView = (ActionBarView)decor.findViewById(R.id.abs__action_bar);
+ mContainerView = (ActionBarContainer)decor.findViewById(R.id.abs__action_bar_container);
- if (mActionView == null) {
+ if (mActionView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used with a screen_*.xml layout");
}
- mFadeInAnimation = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in);
- mFadeOutAnimation = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out);
-
- if (mActionView.getTitle() == null) {
- mActionView.setTitle(mActivity.getTitle());
- }
+ mFadeInAnimation = AnimationUtils.loadAnimation(mContext, android.R.anim.fade_in);
+ mFadeOutAnimation = AnimationUtils.loadAnimation(mContext, android.R.anim.fade_out);
}
- public void onMenuInflated(MenuBuilder menu) {
- if (mActionView == null) {
- return;
- }
-
- final int maxItems = mActivity.getResources().getInteger(R.integer.abs__max_action_buttons);
-
- //Iterate and grab as many actions as we can up to maxItems honoring
- //their showAsAction values
- int ifItems = 0;
- final int count = menu.size();
- boolean showsActionItemText = menu.getShowsActionItemText();
- List keep = new ArrayList();
- for (int i = 0; i < count; i++) {
- MenuItemImpl item = (MenuItemImpl)menu.getItem(i);
-
- //Items without an icon or custom view are forced into the overflow menu
- if (!showsActionItemText && (item.getIcon() == null) && (item.getActionView() == null)) {
- continue;
- }
- if (showsActionItemText && ((item.getTitle() == null) || "".equals(item.getTitle()))) {
- continue;
- }
-
- if ((item.getShowAsAction() & MenuItem.SHOW_AS_ACTION_ALWAYS) != 0) {
- //Show always therefore add to keep list
- keep.add(item);
-
- if ((keep.size() > maxItems) && (ifItems > 0)) {
- //If we have exceeded the max and there are "ifRoom" items
- //then iterate backwards to remove one and add it to the
- //head of the classic items list.
- for (int j = keep.size() - 1; j >= 0; j--) {
- if ((keep.get(j).getShowAsAction() & MenuItem.SHOW_AS_ACTION_IF_ROOM) != 0) {
- keep.remove(j);
- ifItems -= 1;
- break;
- }
- }
- }
- } else if (((item.getShowAsAction() & MenuItem.SHOW_AS_ACTION_IF_ROOM) != 0)
- && (keep.size() < maxItems)) {
- //"ifRoom" items are added if we have not exceeded the max.
- keep.add(item);
- ifItems += 1;
- }
- }
-
- //Mark items that will be shown on the action bar as such so they do
- //not show up on the activity options menu
- mActionView.removeAllItems();
- for (MenuItemImpl item : keep) {
- item.setIsShownOnActionBar(true);
-
- //Get a new item for this menu item
- ActionMenuItemView actionItem = mActionView.newItem();
- actionItem.initialize(item, MenuBuilder.TYPE_ACTION_BAR);
-
- //Associate the itemview with the item so changes will be reflected
- item.setItemView(MenuBuilder.TYPE_ACTION_BAR, actionItem);
-
- //Add to the action bar for display
- mActionView.addItem(actionItem);
- }
+ public void setMenu(MenuBuilder menu, MenuPresenter.Callback cb) {
+ mActionView.setMenu(menu, cb);
}
public void onMenuVisibilityChanged(boolean isVisible) {
@@ -158,12 +90,6 @@ public final class ActionBarImpl extends ActionBar {
}
}
- public void setProgressBarIndeterminateVisibility(boolean visible) {
- if (mActionView != null) {
- mActionView.setProgressBarIndeterminateVisibility(visible);
- }
- }
-
// ------------------------------------------------------------------------
// ACTION MODE METHODS
// ------------------------------------------------------------------------
@@ -328,7 +254,7 @@ public final class ActionBarImpl extends ActionBar {
@Override
public void setCustomView(int resId) {
- View view = LayoutInflater.from(mActivity).inflate(resId, mActionView, false);
+ View view = LayoutInflater.from(mContext).inflate(resId, mActionView, false);
setCustomView(view);
}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
index e35064739..c4393da05 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
@@ -17,6 +17,8 @@
package com.actionbarsherlock.internal.app;
import java.util.HashMap;
+
+import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
import com.actionbarsherlock.internal.view.menu.MenuWrapper;
import android.app.Activity;
@@ -25,7 +27,6 @@ import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBar;
import android.support.v4.view.ActionMode;
import android.support.v4.view.Menu;
-import android.support.v4.view.MenuInflater;
import android.view.View;
import android.widget.SpinnerAdapter;
@@ -70,11 +71,6 @@ public final class ActionBarWrapper {
return mActivity.getActionBar();
}
- @Override
- protected ActionBar getPublicInstance() {
- return (getActionBar() != null) ? this : null;
- }
-
/**
* Converts our Tab wrapper to a native version containing the wrapper
* instance as its tag.
@@ -179,8 +175,8 @@ public final class ActionBarWrapper {
}
@Override
- public MenuInflater getMenuInflater() {
- return new MenuInflater(mContext, null);
+ public MenuInflaterImpl getMenuInflater() {
+ return new MenuInflaterImpl(mContext, null);
}
@Override
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java
new file mode 100644
index 000000000..7d45e81be
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_HasStateListenerSupport.java
@@ -0,0 +1,6 @@
+package com.actionbarsherlock.internal.view;
+
+public interface View_HasStateListenerSupport {
+ void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener);
+ void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener);
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java
new file mode 100644
index 000000000..3869d3290
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/View_OnAttachStateChangeListener.java
@@ -0,0 +1,8 @@
+package com.actionbarsherlock.internal.view;
+
+import android.view.View;
+
+public interface View_OnAttachStateChangeListener {
+ void onViewAttachedToWindow(View v);
+ void onViewDetachedFromWindow(View v);
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java
new file mode 100644
index 000000000..b7650bc11
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenu.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.v4.view.Menu;
+import android.support.v4.view.MenuItem;
+import android.support.v4.view.SubMenu;
+import android.view.KeyEvent;
+
+/**
+ * @hide
+ */
+public class ActionMenu implements Menu {
+ private Context mContext;
+
+ private boolean mIsQwerty;
+
+ private ArrayList mItems;
+
+ public ActionMenu(Context context) {
+ mContext = context;
+ mItems = new ArrayList();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public MenuItem add(CharSequence title) {
+ return add(0, 0, 0, title);
+ }
+
+ public MenuItem add(int titleRes) {
+ return add(0, 0, 0, titleRes);
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, int titleRes) {
+ return add(groupId, itemId, order, mContext.getResources().getString(titleRes));
+ }
+
+ public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
+ ActionMenuItem item = new ActionMenuItem(getContext(),
+ groupId, itemId, 0, order, title);
+ mItems.add(order, item);
+ return item;
+ }
+
+ public int addIntentOptions(int groupId, int itemId, int order,
+ ComponentName caller, Intent[] specifics, Intent intent, int flags,
+ android.view.MenuItem[] outSpecificItems) {
+ PackageManager pm = mContext.getPackageManager();
+ final List lri =
+ pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+ final int N = lri != null ? lri.size() : 0;
+
+ if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+ removeGroup(groupId);
+ }
+
+ for (int i=0; i= 0) {
+ outSpecificItems[ri.specificIndex] = item;
+ }
+ }
+
+ return N;
+ }
+
+ public SubMenu addSubMenu(CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order,
+ CharSequence title) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
+ // TODO Implement submenus
+ return null;
+ }
+
+ public void clear() {
+ mItems.clear();
+ }
+
+ public void close() {
+ }
+
+ private int findItemIndex(int id) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).getItemId() == id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public MenuItem findItem(int id) {
+ return mItems.get(findItemIndex(id));
+ }
+
+ public MenuItem getItem(int index) {
+ return mItems.get(index);
+ }
+
+ public boolean hasVisibleItems() {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (items.get(i).isVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) {
+ // TODO Make this smarter.
+ final boolean qwerty = mIsQwerty;
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ final char shortcut = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
+ if (keyCode == shortcut) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return findItemWithShortcut(keyCode, event) != null;
+ }
+
+ public boolean performIdentifierAction(int id, int flags) {
+ final int index = findItemIndex(id);
+ if (index < 0) {
+ return false;
+ }
+
+ return mItems.get(index).invoke();
+ }
+
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ ActionMenuItem item = findItemWithShortcut(keyCode, event);
+ if (item == null) {
+ return false;
+ }
+
+ return item.invoke();
+ }
+
+ public void removeGroup(int groupId) {
+ final ArrayList items = mItems;
+ int itemCount = items.size();
+ int i = 0;
+ while (i < itemCount) {
+ if (items.get(i).getGroupId() == groupId) {
+ items.remove(i);
+ itemCount--;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ public void removeItem(int id) {
+ mItems.remove(findItemIndex(id));
+ }
+
+ public void setGroupCheckable(int group, boolean checkable,
+ boolean exclusive) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setCheckable(checkable);
+ item.setExclusiveCheckable(exclusive);
+ }
+ }
+ }
+
+ public void setGroupEnabled(int group, boolean enabled) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setEnabled(enabled);
+ }
+ }
+ }
+
+ public void setGroupVisible(int group, boolean visible) {
+ final ArrayList items = mItems;
+ final int itemCount = items.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ ActionMenuItem item = items.get(i);
+ if (item.getGroupId() == group) {
+ item.setVisible(visible);
+ }
+ }
+ }
+
+ public void setQwertyMode(boolean isQwerty) {
+ mIsQwerty = isQwerty;
+ }
+
+ public int size() {
+ return mItems.size();
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java
index 6c32fed30..fdb536138 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItem.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
package com.actionbarsherlock.internal.view.menu;
import android.content.Context;
@@ -5,251 +21,229 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
-import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
+/**
+ * @hide
+ */
public class ActionMenuItem implements MenuItem {
- private static final int CHECKABLE = MenuItemImpl.CHECKABLE;
- private static final int CHECKED = MenuItemImpl.CHECKED;
- private static final int ENABLED = MenuItemImpl.ENABLED;
- private static final int EXCLUSIVE = MenuItemImpl.EXCLUSIVE;
- private static final int HIDDEN = MenuItemImpl.HIDDEN;
- private static final int NO_ICON = 0;
-
- //XXX UNUSED: private final int mCategoryOrder;
- private MenuItem.OnMenuItemClickListener mClickListener;
- private Context mContext;
- private int mFlags = ENABLED;
- private final int mGroup;
- private Drawable mIconDrawable;
- private int mIconResId = NO_ICON;
private final int mId;
- private Intent mIntent;
+ private final int mGroup;
+ //UNUSED private final int mCategoryOrder;
private final int mOrdering;
- private char mShortcutAlphabeticChar;
- private char mShortcutNumericChar;
+
private CharSequence mTitle;
private CharSequence mTitleCondensed;
+ private Intent mIntent;
+ private char mShortcutNumericChar;
+ private char mShortcutAlphabeticChar;
+
+ private Drawable mIconDrawable;
+ //UNUSED private int mIconResId = NO_ICON;
- public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, CharSequence title) {
+ private Context mContext;
+
+ private MenuItem.OnMenuItemClickListener mClickListener;
+
+ //UNUSED private static final int NO_ICON = 0;
+
+ private int mFlags = ENABLED;
+ private static final int CHECKABLE = 0x00000001;
+ private static final int CHECKED = 0x00000002;
+ private static final int EXCLUSIVE = 0x00000004;
+ private static final int HIDDEN = 0x00000008;
+ private static final int ENABLED = 0x00000010;
+
+ public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
+ CharSequence title) {
mContext = context;
mId = id;
mGroup = group;
- //XXX UNUSED mCategoryOrder = categoryOrder;
+ //UNUSED mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
}
- @Override
- public View getActionView() {
- return null;
- }
-
- @Override
public char getAlphabeticShortcut() {
return mShortcutAlphabeticChar;
}
- @Override
public int getGroupId() {
return mGroup;
}
- @Override
public Drawable getIcon() {
return mIconDrawable;
}
- @Override
public Intent getIntent() {
return mIntent;
}
- @Override
public int getItemId() {
return mId;
}
- @Override
- public ContextMenu.ContextMenuInfo getMenuInfo() {
+ public ContextMenuInfo getMenuInfo() {
return null;
}
- @Override
public char getNumericShortcut() {
return mShortcutNumericChar;
}
- @Override
public int getOrder() {
return mOrdering;
}
- @Override
public SubMenu getSubMenu() {
return null;
}
- @Override
public CharSequence getTitle() {
return mTitle;
}
- @Override
public CharSequence getTitleCondensed() {
return mTitleCondensed;
}
- @Override
public boolean hasSubMenu() {
return false;
}
- public boolean invoke() {
- if ((mClickListener != null) && mClickListener.onMenuItemClick(this)) {
- return true;
- } else if (mIntent != null) {
- mContext.startActivity(mIntent);
- return true;
- }
- return false;
- }
-
- @Override
public boolean isCheckable() {
return (mFlags & CHECKABLE) != 0;
}
- @Override
public boolean isChecked() {
return (mFlags & CHECKED) != 0;
}
- @Override
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
- @Override
public boolean isVisible() {
return (mFlags & HIDDEN) == 0;
}
- @Override
- public MenuItem setActionView(int layoutResId) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public MenuItem setActionView(View view) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public MenuItem setAlphabeticShortcut(char shortcut) {
- mShortcutAlphabeticChar = shortcut;
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ mShortcutAlphabeticChar = alphaChar;
return this;
}
- @Override
public MenuItem setCheckable(boolean checkable) {
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
return this;
}
- @Override
+ public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ return this;
+ }
+
public MenuItem setChecked(boolean checked) {
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
return this;
}
- @Override
public MenuItem setEnabled(boolean enabled) {
mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
return this;
}
- public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
- mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
- return this;
- }
-
- @Override
- public MenuItem setIcon(int resId) {
- mIconResId = resId;
- mIconDrawable = mContext.getResources().getDrawable(mIconResId);
+ public MenuItem setIcon(Drawable icon) {
+ mIconDrawable = icon;
+ //UNUSED mIconResId = NO_ICON;
return this;
}
- @Override
- public MenuItem setIcon(Drawable icon) {
- mIconDrawable = icon;
- mIconResId = NO_ICON;
+ public MenuItem setIcon(int iconRes) {
+ //UNUSED mIconResId = iconRes;
+ mIconDrawable = mContext.getResources().getDrawable(iconRes);
return this;
}
- @Override
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
- @Override
- public MenuItem setNumericShortcut(char shortcut) {
- mShortcutNumericChar = shortcut;
+ public MenuItem setNumericShortcut(char numericChar) {
+ mShortcutNumericChar = numericChar;
return this;
}
- @Override
- public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
- mClickListener = listener;
+ public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+ mClickListener = menuItemClickListener;
return this;
}
- @Override
- public android.view.MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener listener) {
- mClickListener = new MenuItem.OnMenuItemClickListener() {
+ public MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener menuItemClickListener) {
+ mClickListener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
- return listener.onMenuItemClick(item);
+ return menuItemClickListener.onMenuItemClick(item);
}
};
- return null;
- }
-
- @Override
- public MenuItem setShortcut(char numericShortcut, char alphabeticShortcut) {
- mShortcutNumericChar = numericShortcut;
- mShortcutAlphabeticChar = alphabeticShortcut;
return this;
}
- @Override
- public void setShowAsAction(int layoutResId) {
- //No op
- }
-
- @Override
- public MenuItem setTitle(int resId) {
- this.mTitle = mContext.getResources().getString(resId);
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = alphaChar;
return this;
}
- @Override
public MenuItem setTitle(CharSequence title) {
mTitle = title;
return this;
}
- @Override
+ public MenuItem setTitle(int title) {
+ mTitle = mContext.getResources().getString(title);
+ return this;
+ }
+
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
return this;
}
- @Override
public MenuItem setVisible(boolean visible) {
mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
return this;
}
-}
\ No newline at end of file
+
+ public boolean invoke() {
+ if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
+ return true;
+ }
+
+ if (mIntent != null) {
+ mContext.startActivity(mIntent);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setShowAsAction(int show) {
+ // Do nothing. ActionMenuItems always show as action buttons.
+ }
+
+ public MenuItem setActionView(View actionView) {
+ throw new UnsupportedOperationException();
+ }
+
+ public View getActionView() {
+ return null;
+ }
+
+ @Override
+ public MenuItem setActionView(int resId) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java
index fa6f8bd56..057ac8803 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuItemView.java
@@ -1,149 +1,245 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
package com.actionbarsherlock.internal.view.menu;
-import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Set;
+
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
+import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
+
+/**
+ * @hide
+ */
+public class ActionMenuItemView extends LinearLayout
+ implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
+ ActionMenuView.ActionMenuChildView, View_HasStateListenerSupport {
+ //UNUSED private static final String TAG = "ActionMenuItemView";
+
+ private MenuItemImpl mItemData;
+ private CharSequence mTitle;
+ private MenuBuilder.ItemInvoker mItemInvoker;
+
+ private ImageButton mImageButton;
+ private Button mTextButton;
+ private boolean mAllowTextWithIcon;
+ //UNUSED private boolean mShowTextAllCaps;
+ private boolean mExpandedFormat;
-public class ActionMenuItemView extends RelativeLayout implements MenuView.ItemView, View.OnClickListener {
- private ImageView mImageButton;
- private TextView mTextButton;
- private FrameLayout mCustomView;
- private MenuItemImpl mMenuItem;
- private WeakReference mDivider;
+ private final Set mListeners = new HashSet();
public ActionMenuItemView(Context context) {
this(context, null);
}
+
public ActionMenuItemView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.actionButtonStyle);
+ this(context, attrs, 0);
}
+
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- setOnClickListener(this);
+ //TODO super(context, attrs, defStyle);
+ super(context, attrs);
+ final Resources res = context.getResources();
+ mAllowTextWithIcon = res.getBoolean(
+ R.bool.abs__config_allowActionMenuItemTextWithIcon);
+ //UNUSED mShowTextAllCaps = res.getBoolean(R.bool.abs__config_actionMenuItemAllCaps);
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.remove(listener);
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewAttachedToWindow(this);
+ }
+ }
- mImageButton = (ImageView) findViewById(R.id.abs__item_icon);
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewDetachedFromWindow(this);
+ }
+ }
+
+ @Override
+ public void onFinishInflate() {
+
+ mImageButton = (ImageButton) findViewById(R.id.abs__imageButton);
+ mTextButton = (Button) findViewById(R.id.abs__textButton);
mImageButton.setOnClickListener(this);
- mTextButton = (TextView) findViewById(R.id.abs__item_text);
mTextButton.setOnClickListener(this);
- mCustomView = (FrameLayout) findViewById(R.id.abs__item_custom);
- mCustomView.setOnClickListener(this);
+ mImageButton.setOnLongClickListener(this);
+ setOnClickListener(this);
+ setOnLongClickListener(this);
+ }
+
+ public MenuItemImpl getItemData() {
+ return mItemData;
}
+ public void initialize(MenuItemImpl itemData, int menuType) {
+ mItemData = itemData;
+
+ setIcon(itemData.getIcon());
+ setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon
+ setId(itemData.getItemId());
+
+ setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+ setEnabled(itemData.isEnabled());
+ }
+
+ @Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mImageButton.setEnabled(enabled);
mTextButton.setEnabled(enabled);
- mCustomView.setEnabled(enabled);
}
- public void setDivider(ImageView divider) {
- mDivider = new WeakReference(divider);
- //Ensure we are not displaying the divider when we are not visible
- setDividerVisibility(getVisibility());
+ public void onClick(View v) {
+ if (mItemInvoker != null) {
+ mItemInvoker.invokeItem(mItemData);
+ }
}
- public void setVisible(boolean visible) {
- final int visibility = visible ? View.VISIBLE : View.GONE;
- setDividerVisibility(visibility);
- setVisibility(visibility);
+ public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
+ mItemInvoker = invoker;
}
- private void setDividerVisibility(int visibility) {
- if ((mDivider != null) && (mDivider.get() != null)) {
- mDivider.get().setVisibility(visibility);
- }
+ public boolean prefersCondensedTitle() {
+ return true;
}
- public void reloadDisplay() {
- final boolean hasCustomView = mCustomView.getChildCount() > 0;
- final boolean hasText = mMenuItem.showsActionItemText() && !"".equals(mTextButton.getText());
-
- if (hasCustomView) {
- mCustomView.setVisibility(View.VISIBLE);
- mImageButton.setVisibility(View.GONE);
- mTextButton.setVisibility(View.GONE);
- } else {
- mCustomView.setVisibility(View.GONE);
- mImageButton.setVisibility(View.VISIBLE);
- mTextButton.setVisibility(hasText ? View.VISIBLE : View.GONE);
- }
+ public void setCheckable(boolean checkable) {
+ // TODO Support checkable action items
}
- public void setIcon(Drawable icon) {
- mImageButton.setImageDrawable(icon);
+ public void setChecked(boolean checked) {
+ // TODO Support checkable action items
}
- public void setTitle(CharSequence title) {
- mTextButton.setText(title);
- reloadDisplay();
+ public void setExpandedFormat(boolean expandedFormat) {
+ if (mExpandedFormat != expandedFormat) {
+ mExpandedFormat = expandedFormat;
+ if (mItemData != null) {
+ mItemData.actionFormatChanged();
+ }
+ }
}
- @Override
- public void initialize(MenuItemImpl itemData, int menuType) {
- mMenuItem = itemData;
- setId(itemData.getItemId());
- setIcon(itemData.getIcon());
- setTitle(itemData.getTitle());
- setEnabled(itemData.isEnabled());
- setActionView(itemData.getActionView());
- setVisible(itemData.isVisible());
- }
+ private void updateTextButtonVisibility() {
+ boolean visible = !TextUtils.isEmpty(mTextButton.getText());
+ visible &= mImageButton.getDrawable() == null ||
+ (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
- @Override
- public MenuItemImpl getItemData() {
- return mMenuItem;
+ mTextButton.setVisibility(visible ? VISIBLE : GONE);
}
- @Override
- public void setCheckable(boolean checkable) {
- // No-op
+ public void setIcon(Drawable icon) {
+ mImageButton.setImageDrawable(icon);
+ if (icon != null) {
+ mImageButton.setVisibility(VISIBLE);
+ } else {
+ mImageButton.setVisibility(GONE);
+ }
+
+ updateTextButtonVisibility();
}
- @Override
- public void setChecked(boolean checked) {
- // No-op
+ public boolean hasText() {
+ return mTextButton.getVisibility() != GONE;
}
- @Override
public void setShortcut(boolean showShortcut, char shortcutKey) {
- // No-op
+ // Action buttons don't show text for shortcut keys.
}
- @Override
- public void setActionView(View actionView) {
- mCustomView.removeAllViews();
- if (actionView != null) {
- mCustomView.addView(actionView);
- }
- reloadDisplay();
- }
+ public void setTitle(CharSequence title) {
+ mTitle = title;
- @Override
- public boolean prefersCondensedTitle() {
- return true;
+ mTextButton.setText(mTitle);
+
+ setContentDescription(mTitle);
+ updateTextButtonVisibility();
}
- @Override
public boolean showsIcon() {
return true;
}
+ public boolean needsDividerBefore() {
+ return hasText() && mItemData.getIcon() == null;
+ }
+
+ public boolean needsDividerAfter() {
+ return hasText();
+ }
+
@Override
- public void onClick(View v) {
- if (mMenuItem != null) {
- mMenuItem.invoke();
+ public boolean onLongClick(View v) {
+ if (hasText()) {
+ // Don't show the cheat sheet for items that already show text.
+ return false;
}
+
+ final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
+ getLocationOnScreen(screenPos);
+ getWindowVisibleDisplayFrame(displayFrame);
+
+ final Context context = getContext();
+ final int width = getWidth();
+ final int height = getHeight();
+ final int midy = screenPos[1] + height / 2;
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+ Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
+ screenWidth - screenPos[0] - width / 2, height);
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
+ cheatSheet.show();
+ return true;
}
}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 000000000..e0ccdf935
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,448 @@
+/*
+ * 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.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.MenuItem;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+
+import com.actionbarsherlock.R;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter {
+ //UNUSED private static final String TAG = "ActionMenuPresenter";
+
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+ private boolean mMaxItemsSet;
+ private boolean mStrictWidthLimit;
+ private boolean mWidthLimitSet;
+
+ private int mMinCellSize;
+
+ private AlertDialog mDialog;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+ int mOpenSubMenuId;
+
+ public ActionMenuPresenter(Context context) {
+ super(context, R.layout.abs__action_menu_layout,
+ R.layout.abs__action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+
+ if (!mWidthLimitSet) {
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+ }
+
+ // Measure for initial configuration
+ if (!mMaxItemsSet) {
+ mMaxItems = res.getInteger(R.integer.abs__max_action_buttons);
+ }
+
+ mActionItemWidthLimit = mWidthLimit;
+
+ mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (!mMaxItemsSet) {
+ mMaxItems = mContext.getResources().getInteger(
+ R.integer.abs__max_action_buttons);
+ if (mMenu != null) {
+ mMenu.onItemsChanged(true);
+ }
+ }
+ }
+
+ public void setWidthLimit(int width, boolean strict) {
+ mWidthLimit = width;
+ mStrictWidthLimit = strict;
+ mWidthLimitSet = true;
+ }
+
+ public void setItemLimit(int itemCount) {
+ mMaxItems = itemCount;
+ mMaxItemsSet = true;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ View actionView = item.getActionView();
+ if (actionView == null) {
+ if (!(convertView instanceof ActionMenuItemView)) {
+ convertView = null;
+ }
+ actionView = super.getItemView(item, convertView, parent);
+ }
+ actionView.setVisibility(View.VISIBLE);
+
+ final ActionMenuView menuParent = (ActionMenuView) parent;
+ final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+ if (!menuParent.checkLayoutParams(lp)) {
+ actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+ }
+ return actionView;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+
+ final ActionMenuView menuView = (ActionMenuView) mMenuView;
+ ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
+ actionItemView.setItemInvoker(menuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) {
+ return false;
+ }
+
+ mOpenSubMenuId = subMenu.getItem().getItemId();
+
+ final List items = subMenu.getVisibleItems();
+ final int itemsSize = items.size();
+ final CharSequence[] itemText = new CharSequence[itemsSize];
+ for (int i = 0; i < itemsSize; i++) {
+ itemText[i] = items.get(i).getTitle();
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+ .setTitle(subMenu.getItem().getTitle())
+ .setIcon(subMenu.getItem().getIcon())
+ .setCancelable(true)
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mDialog = null;
+ }
+ });
+
+ final boolean isExclusive = ((MenuItemImpl)subMenu.getItem(0)).isExclusiveCheckable();
+ final boolean isCheckable = ((MenuItemImpl)subMenu.getItem(0)).isCheckable();
+ if (isExclusive) {
+ int selected = -1;
+ for (int i = 0; i < itemsSize; i++) {
+ if (items.get(i).isChecked()) {
+ selected = i;
+ break;
+ }
+ }
+ builder.setSingleChoiceItems(itemText, selected, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ items.get(which).invoke();
+ //dialog.dismiss();
+ mDialog = null;
+ }
+ });
+ } else if (isCheckable) {
+ boolean[] selected = new boolean[itemsSize];
+ for (int i = 0; i < itemsSize; i++) {
+ selected[i] = items.get(i).isChecked();
+ }
+ builder.setMultiChoiceItems(itemText, selected, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ items.get(which).setChecked(isChecked);
+ //dialog.dismiss();
+ mDialog = null;
+ }
+ });
+ } else {
+ builder.setItems(itemText, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ items.get(which).invoke();
+ //dialog.dismiss();
+ mDialog = null;
+ }
+ });
+ }
+
+ mDialog = builder.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ return hideSubMenus();
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mDialog != null) {
+ try {
+ mDialog.dismiss();
+ } catch (Exception e) {
+ //Must have been dismissed or cancelled already
+ }
+ mDialog = null;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ //int requestedItems = 0;
+ int firstActionWidth = 0;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ }/* else if (item.requestsActionButton()) {
+ requestedItems++;
+ }*/
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ int cellSize = 0;
+ int cellsRemaining = 0;
+ if (mStrictWidthLimit) {
+ cellsRemaining = widthLimit / mMinCellSize;
+ final int cellSizeRemaining = widthLimit % mMinCellSize;
+ cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
+ }
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ cellsRemaining -= ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ item.setIsActionButton(true);
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
+ (!mStrictWidthLimit || cellsRemaining > 0);
+
+ if (isAction) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ final int cells = ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ cellsRemaining -= cells;
+ if (cells == 0) {
+ isAction = false;
+ }
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ if (mStrictWidthLimit) {
+ isAction &= widthLimit >= 0;
+ } else {
+ // Did this push the entire first item past the limit?
+ isAction &= widthLimit + firstActionWidth > 0;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ // Give back the action slot
+ if (areYouMyGroupie.isActionButton()) maxActions++;
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ if (isAction) maxActions--;
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState();
+ state.openSubMenuId = mOpenSubMenuId;
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState saved = (SavedState) state;
+ if (saved.openSubMenuId > 0) {
+ MenuItem item = mMenu.findItem(saved.openSubMenuId);
+ if (item != null) {
+ SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ onSubMenuSelected(subMenu);
+ }
+ }
+ }
+
+ private static class SavedState implements Parcelable {
+ public int openSubMenuId;
+
+ SavedState() {
+ }
+
+ SavedState(Parcel in) {
+ openSubMenuId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(openSubMenuId);
+ }
+
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
new file mode 100644
index 000000000..f1ea4f472
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+package com.actionbarsherlock.internal.view.menu;
+
+import com.actionbarsherlock.internal.widget.IcsLinearLayout;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * @hide
+ */
+public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView {
+ //UNUSED private static final String TAG = "ActionMenuView";
+
+ static final int MIN_CELL_SIZE = 56; // dips
+ static final int GENERATED_ITEM_PADDING = 4; // dips
+
+ private MenuBuilder mMenu;
+
+ private boolean mReserveOverflow;
+ private ActionMenuPresenter mPresenter;
+ private boolean mFormatItems;
+ private int mFormatItemsWidth;
+ private int mMinCellSize;
+ private int mGeneratedItemPadding;
+ //UNUSED private int mMeasuredExtraWidth;
+
+ public ActionMenuView(Context context) {
+ this(context, null);
+ }
+
+ public ActionMenuView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setBaselineAligned(false);
+ final float density = context.getResources().getDisplayMetrics().density;
+ mMinCellSize = (int) (MIN_CELL_SIZE * density);
+ mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+ }
+
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ public boolean isExpandedFormat() {
+ return mFormatItems;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ super.onConfigurationChanged(newConfig);
+ //TODO figure out a way to call this pre-2.2
+ }
+ mPresenter.updateMenuView(false);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // If we've been given an exact size to match, apply special formatting during layout.
+ final boolean wasFormatted = mFormatItems;
+ mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+
+ if (wasFormatted != mFormatItems) {
+ mFormatItemsWidth = 0; // Reset this when switching modes
+ }
+
+ // Special formatting can change whether items can fit as action buttons.
+ // Kick the menu and update presenters when this changes.
+ final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
+ if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
+ mFormatItemsWidth = widthSize;
+ mMenu.onItemsChanged(true);
+ }
+
+ if (mFormatItems) {
+ onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
+ // We already know the width mode is EXACTLY if we're here.
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ final int heightPadding = getPaddingTop() + getPaddingBottom();
+
+ widthSize -= widthPadding;
+
+ // Divide the view into cells.
+ final int cellCount = widthSize / mMinCellSize;
+ final int cellSizeRemaining = widthSize % mMinCellSize;
+
+ if (cellCount == 0) {
+ // Give up, nothing fits.
+ setMeasuredDimension(widthSize, 0);
+ return;
+ }
+
+ final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
+
+ int cellsRemaining = cellCount;
+ int maxChildHeight = 0;
+ int maxCellsUsed = 0;
+ int expandableItemCount = 0;
+ int visibleItemCount = 0;
+ boolean hasOverflow = false;
+
+ // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
+ long smallestItemsAt = 0;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) continue;
+
+ final boolean isGeneratedItem = child instanceof ActionMenuItemView;
+ visibleItemCount++;
+
+ if (isGeneratedItem) {
+ // Reset padding for generated menu item views; it may change below
+ // and views are recycled.
+ child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.expanded = false;
+ lp.extraPixels = 0;
+ lp.cellsUsed = 0;
+ lp.expandable = false;
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
+
+ // Overflow always gets 1 cell. No more, no less.
+ final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
+
+ final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
+ heightMeasureSpec, heightPadding);
+
+ maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
+ if (lp.expandable) expandableItemCount++;
+ if (lp.isOverflowButton) hasOverflow = true;
+
+ cellsRemaining -= cellsUsed;
+ maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
+ if (cellsUsed == 1) smallestItemsAt |= (1 << i);
+ }
+
+ // When we have overflow and a single expanded (text) item, we want to try centering it
+ // visually in the available space even though overflow consumes some of it.
+ final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
+
+ // Divide space for remaining cells if we have items that can expand.
+ // Try distributing whole leftover cells to smaller items first.
+
+ boolean needsExpansion = false;
+ while (expandableItemCount > 0 && cellsRemaining > 0) {
+ int minCells = Integer.MAX_VALUE;
+ long minCellsAt = 0; // Bit locations are indices of relevant child views
+ int minCellsItemCount = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ // Don't try to expand items that shouldn't.
+ if (!lp.expandable) continue;
+
+ // Mark indices of children that can receive an extra cell.
+ if (lp.cellsUsed < minCells) {
+ minCells = lp.cellsUsed;
+ minCellsAt = 1 << i;
+ minCellsItemCount = 1;
+ } else if (lp.cellsUsed == minCells) {
+ minCellsAt |= 1 << i;
+ minCellsItemCount++;
+ }
+ }
+
+ // Items that get expanded will always be in the set of smallest items when we're done.
+ smallestItemsAt |= minCellsAt;
+
+ if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
+
+ // We have enough cells, all minimum size items will be incremented.
+ minCells++;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if ((minCellsAt & (1 << i)) == 0) {
+ // If this item is already at our small item count, mark it for later.
+ if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
+ continue;
+ }
+
+ if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
+ // Add padding to this item such that it centers.
+ child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
+ }
+ lp.cellsUsed++;
+ lp.expanded = true;
+ cellsRemaining--;
+ }
+
+ needsExpansion = true;
+ }
+
+ // Divide any space left that wouldn't divide along cell boundaries
+ // evenly among the smallest items
+
+ final boolean singleItem = !hasOverflow && visibleItemCount == 1;
+ if (cellsRemaining > 0 && smallestItemsAt != 0 &&
+ (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
+ float expandCount = Long.bitCount(smallestItemsAt);
+
+ if (!singleItem) {
+ // The items at the far edges may only expand by half in order to pin to either side.
+ if ((smallestItemsAt & 1) != 0) {
+ LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
+ LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ }
+
+ final int extraPixels = expandCount > 0 ?
+ (int) (cellsRemaining * cellSize / expandCount) : 0;
+
+ for (int i = 0; i < childCount; i++) {
+ if ((smallestItemsAt & (1 << i)) == 0) continue;
+
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child instanceof ActionMenuItemView) {
+ // If this is one of our views, expand and measure at the larger size.
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ if (i == 0 && !lp.preventEdgeOffset) {
+ // First item gets part of its new padding pushed out of sight.
+ // The last item will get this implicitly from layout.
+ lp.leftMargin = -extraPixels / 2;
+ }
+ needsExpansion = true;
+ } else if (lp.isOverflowButton) {
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ lp.rightMargin = -extraPixels / 2;
+ needsExpansion = true;
+ } else {
+ // If we don't know what it is, give it some margins instead
+ // and let it center within its space. We still want to pin
+ // against the edges.
+ if (i != 0) {
+ lp.leftMargin = extraPixels / 2;
+ }
+ if (i != childCount - 1) {
+ lp.rightMargin = extraPixels / 2;
+ }
+ }
+ }
+
+ cellsRemaining = 0;
+ }
+
+ // Remeasure any items that have had extra space allocated to them.
+ if (needsExpansion) {
+ int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode);
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (!lp.expanded) continue;
+
+ final int width = lp.cellsUsed * cellSize + lp.extraPixels;
+ child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
+ }
+ }
+
+ if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = maxChildHeight;
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
+ }
+
+ /**
+ * Measure a child view to fit within cell-based formatting. The child's width
+ * will be measured to a whole multiple of cellSize.
+ *
+ * Sets the expandable and cellsUsed fields of LayoutParams.
+ *
+ * @param child Child to measure
+ * @param cellSize Size of one cell
+ * @param cellsRemaining Number of cells remaining that this view can expand to fill
+ * @param parentHeightMeasureSpec MeasureSpec used by the parent view
+ * @param parentHeightPadding Padding present in the parent view
+ * @return Number of cells this child was measured to occupy
+ */
+ static int measureChildForCells(View child, int cellSize, int cellsRemaining,
+ int parentHeightMeasureSpec, int parentHeightPadding) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
+ parentHeightPadding;
+ final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
+
+ int cellsUsed = 0;
+ if (cellsRemaining > 0) {
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(
+ cellSize * cellsRemaining, MeasureSpec.AT_MOST);
+ child.measure(childWidthSpec, childHeightSpec);
+
+ final int measuredWidth = child.getMeasuredWidth();
+ cellsUsed = measuredWidth / cellSize;
+ if (measuredWidth % cellSize != 0) cellsUsed++;
+ }
+
+ final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
+ (ActionMenuItemView) child : null;
+ final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText();
+ lp.expandable = expandable;
+
+ lp.cellsUsed = cellsUsed;
+ final int targetWidth = cellsUsed * cellSize;
+ child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ childHeightSpec);
+ return cellsUsed;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mFormatItems) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ final int midVertical = (top + bottom) / 2;
+ final int dividerWidth = getDividerWidth();
+ int overflowWidth = 0;
+ //UNUSED int nonOverflowWidth = 0;
+ int nonOverflowCount = 0;
+ int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+ boolean hasOverflow = false;
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ LayoutParams p = (LayoutParams) v.getLayoutParams();
+ if (p.isOverflowButton) {
+ overflowWidth = v.getMeasuredWidth();
+ if (hasDividerBeforeChildAt(i)) {
+ overflowWidth += dividerWidth;
+ }
+
+ int height = v.getMeasuredHeight();
+ int r = getWidth() - getPaddingRight() - p.rightMargin;
+ int l = r - overflowWidth;
+ int t = midVertical - (height / 2);
+ int b = t + height;
+ v.layout(l, t, r, b);
+
+ widthRemaining -= overflowWidth;
+ hasOverflow = true;
+ } else {
+ final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+ //UNUSED nonOverflowWidth += size;
+ widthRemaining -= size;
+ if (hasDividerBeforeChildAt(i)) {
+ //UNUSED nonOverflowWidth += dividerWidth;
+ }
+ nonOverflowCount++;
+ }
+ }
+
+ if (childCount == 1 && !hasOverflow) {
+ // Center a single child
+ final View v = getChildAt(0);
+ final int width = v.getMeasuredWidth();
+ final int height = v.getMeasuredHeight();
+ final int midHorizontal = (right - left) / 2;
+ final int l = midHorizontal - width / 2;
+ final int t = midVertical - height / 2;
+ v.layout(l, t, l + width, t + height);
+ return;
+ }
+
+ final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
+ final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
+
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin + spacerSize;
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPresenter.dismissPopupMenus();
+ }
+
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public void setOverflowReserved(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER_VERTICAL;
+ return params;
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (p instanceof LayoutParams) {
+ LayoutParams result = new LayoutParams((LayoutParams) p);
+ if (result.gravity <= Gravity.NO_GRAVITY) {
+ result.gravity = Gravity.CENTER_VERTICAL;
+ }
+ return result;
+ }
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p != null && p instanceof LayoutParams;
+ }
+
+ public LayoutParams generateOverflowButtonLayoutParams() {
+ LayoutParams result = generateDefaultLayoutParams();
+ result.isOverflowButton = true;
+ return result;
+ }
+
+ public boolean invokeItem(MenuItemImpl item) {
+ return mMenu.performItemAction(item, 0);
+ }
+
+ public int getWindowAnimations() {
+ return 0;
+ }
+
+ public void initialize(MenuBuilder menu) {
+ mMenu = menu;
+ }
+
+ @Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
+ }
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
+ }
+ return result;
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
+ }
+
+ public static class LayoutParams extends IcsLinearLayout.LayoutParams {
+ public boolean isOverflowButton;
+ public int cellsUsed;
+ public int extraPixels;
+ public boolean expandable;
+ public boolean preventEdgeOffset;
+
+ public boolean expanded;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super((IcsLinearLayout.LayoutParams) other);
+ isOverflowButton = other.isOverflowButton;
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ isOverflowButton = false;
+ }
+
+ public LayoutParams(int width, int height, boolean isOverflowButton) {
+ super(width, height);
+ this.isOverflowButton = isOverflowButton;
+ }
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 000000000..041aec36e
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ protected Context mSystemContext;
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mSystemInflater;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ private int mId;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param context Context for generating system-supplied views
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
+ mSystemContext = context;
+ mSystemInflater = LayoutInflater.from(context);
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return;
+
+ int childIndex = 0;
+ if (mMenu != null) {
+ mMenu.flagActionItems();
+ ArrayList visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final View itemView = getItemView(item, convertView, parent);
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ final ViewGroup currentParent = (ViewGroup) itemView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(itemView);
+ }
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Intended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java
index cb0a7556d..4348e6398 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuBuilder.java
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
- * Copyright (C) 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,189 +16,567 @@
package com.actionbarsherlock.internal.view.menu;
+
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcelable;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
+import android.support.v4.view.SubMenu;
+import android.util.SparseArray;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.View;
/**
- * An implementation of the {@link android.view.Menu} interface for use in
- * inflating menu XML resources to be added to a third-party action bar.
- *
- * @author Jake Wharton
- * @see com.android.internal.view.menu.MenuBuilder
+ * Implementation of the {@link android.view.Menu} interface for creating a
+ * standard menu UI.
*/
public class MenuBuilder implements Menu {
- private static final int DEFAULT_ITEM_ID = 0;
- private static final int DEFAULT_GROUP_ID = 0;
- private static final int DEFAULT_ORDER = 0;
+ //UNUSED private static final String TAG = "MenuBuilder";
+
+ private static final String PRESENTER_KEY = "android:menu:presenters";
+ private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates";
+
+ private static final int[] sCategoryToOrder = new int[] {
+ 1, /* No category */
+ 4, /* CONTAINER */
+ 5, /* SYSTEM */
+ 3, /* SECONDARY */
+ 2, /* ALTERNATIVE */
+ 0, /* SELECTED_ALTERNATIVE */
+ };
- public static final int NUM_TYPES = 2;
- public static final int TYPE_ACTION_BAR = 0;
- public static final int TYPE_NATIVE = 1;
+ private final Context mContext;
+ private final Resources mResources;
/**
- * This is the part of an order integer that the user can provide.
- * @hide
+ * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
+ * instead of accessing this directly.
*/
- static final int USER_MASK = 0x0000ffff;
+ private boolean mQwertyMode;
/**
- * Bit shift of the user portion of the order integer.
- * @hide
+ * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
+ * instead of accessing this directly.
*/
- static final int USER_SHIFT = 0;
+ private boolean mShortcutsVisible;
/**
- * This is the part of an order integer that supplies the category of the
- * item.
- * @hide
+ * Callback that will receive the various menu-related events generated by
+ * this class. Use getCallback to get a reference to the callback.
*/
+ private Callback mCallback;
- static final int CATEGORY_MASK = 0xffff0000;
+ /** Contains all of the items for this menu */
+ private ArrayList mItems;
+ /** Contains only the items that are currently visible. This will be created/refreshed from
+ * {@link #getVisibleItems()} */
+ private ArrayList mVisibleItems;
/**
- * Bit shift of the category portion of the order integer.
- * @hide
+ * Whether or not the items (or any one item's shown state) has changed since it was last
+ * fetched from {@link #getVisibleItems()}
*/
- static final int CATEGORY_SHIFT = 16;
+ private boolean mIsVisibleItemsStale;
- private static final int[] CATEGORY_TO_ORDER = new int[] {
- 1, /* No category */
- 4, /* CONTAINER */
- 5, /* SYSTEM */
- 3, /* SECONDARY */
- 2, /* ALTERNATIVE */
- 0, /* SELECTED_ALTERNATIVE */
- };
+ /**
+ * Contains only the items that should appear in the Action Bar, if present.
+ */
+ private ArrayList mActionItems;
+ /**
+ * Contains items that should NOT appear in the Action Bar, if present.
+ */
+ private ArrayList mNonActionItems;
+
+ /**
+ * Whether or not the items (or any one item's action state) has changed since it was
+ * last fetched.
+ */
+ private boolean mIsActionItemsStale;
+
+ /**
+ * Default value for how added items should show in the action list.
+ */
+ private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
+ /**
+ * Current use case is Context Menus: As Views populate the context menu, each one has
+ * extra information that should be passed along. This is the current menu info that
+ * should be set on all items added to this menu.
+ */
+ private ContextMenuInfo mCurrentMenuInfo;
+
+ /** Header title for menu types that have a header (context and submenus) */
+ CharSequence mHeaderTitle;
+ /** Header icon for menu types that have a header and support icons (context) */
+ Drawable mHeaderIcon;
+ /** Header custom view for menu types that have a header and support custom views (context) */
+ View mHeaderView;
+
+ /**
+ * Contains the state of the View hierarchy for all menu views when the menu
+ * was frozen.
+ */
+ //UNUSED private SparseArray mFrozenViewStates;
+
+ /**
+ * Prevents onItemsChanged from doing its junk, useful for batching commands
+ * that may individually call onItemsChanged.
+ */
+ private boolean mPreventDispatchingItemsChanged = false;
+ private boolean mItemsChangedWhileDispatchPrevented = false;
+
+ private boolean mOptionalIconsVisible = false;
+
+ private boolean mIsClosing = false;
+
+ private ArrayList mTempShortcutItemList = new ArrayList();
+ private CopyOnWriteArrayList> mPresenters =
+ new CopyOnWriteArrayList>();
+ /**
+ * Currently expanded menu item; must be collapsed when we clear.
+ */
+ private MenuItemImpl mExpandedItem;
+
+ /**
+ * Called by menu to notify of close and selection changes.
+ */
public interface Callback {
+ /**
+ * Called when a menu item is selected.
+ * @param menu The menu that is the parent of the item
+ * @param item The menu item that is selected
+ * @return whether the menu item selection was handled
+ */
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
+
+ /**
+ * Called when the mode of the menu changes (for example, from icon to expanded).
+ *
+ * @param menu the menu that has changed modes
+ */
+ public void onMenuModeChange(MenuBuilder menu);
}
+ /**
+ * Called by menu items to execute their associated action
+ */
+ public interface ItemInvoker {
+ public boolean invokeItem(MenuItemImpl item);
+ }
+ public MenuBuilder(Context context) {
+ mContext = context;
+ mResources = context.getResources();
- /** Context used for resolving any resources. */
- private final Context mContext;
+ mItems = new ArrayList();
- /** Child {@link ActionBarMenuItem} items. */
- private final ArrayList mItems;
+ mVisibleItems = new ArrayList();
+ mIsVisibleItemsStale = true;
- /** Menu callback that will receive various events. */
- private Callback mCallback;
+ mActionItems = new ArrayList();
+ mNonActionItems = new ArrayList();
+ mIsActionItemsStale = true;
- private boolean mShowsActionItemText;
+ setShortcutsVisibleInner(true);
+ }
+ /** Bind the non-action items to a native menu. */
+ public boolean bindOverflowToNative(android.view.Menu menu, android.view.MenuItem.OnMenuItemClickListener listener, HashMap map) {
+ final ArrayList nonActionItems = getNonActionItems();
+ if (nonActionItems == null || nonActionItems.size() == 0) {
+ return false;
+ }
+ menu.clear();
+ boolean visible = false;
+ for (MenuItemImpl nonActionItem : nonActionItems) {
+ if (nonActionItem.isVisible()) {
+ visible = true;
+
+ android.view.MenuItem nativeItem;
+ if (nonActionItem.hasSubMenu()) {
+ android.view.SubMenu nativeSub = menu.addSubMenu(nonActionItem.getGroupId(), nonActionItem.getItemId(),
+ nonActionItem.getOrder(), nonActionItem.getTitle());
+
+ SubMenuBuilder subMenu = (SubMenuBuilder)nonActionItem.getSubMenu();
+ for (MenuItemImpl subItem : subMenu.getVisibleItems()) {
+ android.view.MenuItem nativeSubItem = nativeSub.add(subItem.getGroupId(), subItem.getItemId(),
+ subItem.getOrder(), subItem.getTitle());
+
+ nativeSubItem.setIcon(subItem.getIcon());
+ nativeSubItem.setOnMenuItemClickListener(listener);
+ nativeSubItem.setEnabled(subItem.isEnabled());
+ nativeSubItem.setIntent(subItem.getIntent());
+ nativeSubItem.setNumericShortcut(subItem.getNumericShortcut());
+ nativeSubItem.setAlphabeticShortcut(subItem.getAlphabeticShortcut());
+ nativeSubItem.setTitleCondensed(subItem.getTitleCondensed());
+ nativeSubItem.setCheckable(subItem.isCheckable());
+ nativeSubItem.setChecked(subItem.isChecked());
+
+ if (subItem.isExclusiveCheckable()) {
+ nativeSub.setGroupCheckable(subItem.getGroupId(), true, true);
+ }
+
+ map.put(nativeSubItem, subItem);
+ }
+
+ nativeItem = nativeSub.getItem();
+ } else {
+ nativeItem = menu.add(nonActionItem.getGroupId(), nonActionItem.getItemId(),
+ nonActionItem.getOrder(), nonActionItem.getTitle());
+ }
+ nativeItem.setIcon(nonActionItem.getIcon());
+ nativeItem.setOnMenuItemClickListener(listener);
+ nativeItem.setEnabled(nonActionItem.isEnabled());
+ nativeItem.setIntent(nonActionItem.getIntent());
+ nativeItem.setNumericShortcut(nonActionItem.getNumericShortcut());
+ nativeItem.setAlphabeticShortcut(nonActionItem.getAlphabeticShortcut());
+ nativeItem.setTitleCondensed(nonActionItem.getTitleCondensed());
+ nativeItem.setCheckable(nonActionItem.isCheckable());
+ nativeItem.setChecked(nonActionItem.isChecked());
+
+ if (nonActionItem.isExclusiveCheckable()) {
+ menu.setGroupCheckable(nonActionItem.getGroupId(), true, true);
+ }
+
+ map.put(nativeItem, nonActionItem);
+ }
+ }
+
+ return visible;
+ }
+
+ public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
+ mDefaultShowAsAction = defaultShowAsAction;
+ return this;
+ }
/**
- * Create a new action bar menu.
+ * Add a presenter to this menu. This will only hold a WeakReference;
+ * you do not need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
*
- * @param context Context used if resource resolution is required.
+ * @param presenter The presenter to add
*/
- public MenuBuilder(Context context) {
- this.mContext = context;
- this.mItems = new ArrayList();
+ public void addMenuPresenter(MenuPresenter presenter) {
+ mPresenters.add(new WeakReference(presenter));
+ presenter.initForMenu(mContext, this);
+ mIsActionItemsStale = true;
}
-
/**
- * Adds an item to the menu. The other add methods funnel to this.
+ * Remove a presenter from this menu. That presenter will no longer
+ * receive notifications of updates to this menu's data.
*
- * @param itemId Unique item ID.
- * @param groupId Group ID.
- * @param order Order.
- * @param title Item title.
- * @return MenuItem instance.
+ * @param presenter The presenter to remove
*/
- private MenuItem addInternal(int itemId, int groupId, int order, CharSequence title) {
- final int ordering = getOrdering(order);
- final MenuItemImpl item = new MenuItemImpl(this, groupId, itemId, order, ordering, title, MenuItem.SHOW_AS_ACTION_NEVER);
+ public void removeMenuPresenter(MenuPresenter presenter) {
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter item = ref.get();
+ if (item == null || item == presenter) {
+ mPresenters.remove(ref);
+ }
+ }
+ }
- mItems.add(findInsertIndex(mItems, ordering), item);
- return item;
+ private void dispatchPresenterUpdate(boolean cleared) {
+ if (mPresenters.isEmpty()) return;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.updateMenuView(cleared);
+ }
+ }
+ startDispatchingItemsChanged();
}
- private static int findInsertIndex(ArrayList items, int ordering) {
- for (int i = items.size() - 1; i >= 0; i--) {
- MenuItemImpl item = items.get(i);
- if (item.getOrdering() <= ordering) {
- return i + 1;
+ private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean result = false;
+
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if (!result) {
+ result = presenter.onSubMenuSelected(subMenu);
}
}
+ return result;
+ }
- return 0;
+ private void dispatchSaveInstanceState(Bundle outState) {
+ if (mPresenters.isEmpty()) return;
+
+ SparseArray presenterStates = new SparseArray();
+
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ final int id = presenter.getId();
+ if (id > 0) {
+ final Parcelable state = presenter.onSaveInstanceState();
+ if (state != null) {
+ presenterStates.put(id, state);
+ }
+ }
+ }
+ }
+
+ outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates);
+ }
+
+ private void dispatchRestoreInstanceState(Bundle state) {
+ SparseArray presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
+
+ if (presenterStates == null || mPresenters.isEmpty()) return;
+
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ final int id = presenter.getId();
+ if (id > 0) {
+ Parcelable parcel = presenterStates.get(id);
+ if (parcel != null) {
+ presenter.onRestoreInstanceState(parcel);
+ }
+ }
+ }
+ }
+ }
+
+ public void savePresenterStates(Bundle outState) {
+ dispatchSaveInstanceState(outState);
+ }
+
+ public void restorePresenterStates(Bundle state) {
+ dispatchRestoreInstanceState(state);
+ }
+
+ public void saveActionViewStates(Bundle outStates) {
+ SparseArray viewStates = null;
+
+ final int itemCount = size();
+ for (int i = 0; i < itemCount; i++) {
+ final MenuItem item = getItem(i);
+ final View v = item.getActionView();
+ if (v != null && v.getId() != View.NO_ID) {
+ if (viewStates == null) {
+ viewStates = new SparseArray();
+ }
+ v.saveHierarchyState(viewStates);
+ }
+ if (item.hasSubMenu()) {
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ subMenu.saveActionViewStates(outStates);
+ }
+ }
+
+ if (viewStates != null) {
+ outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates);
+ }
+ }
+
+ public void restoreActionViewStates(Bundle states) {
+ if (states == null) {
+ return;
+ }
+
+ SparseArray viewStates = states.getSparseParcelableArray(
+ getActionViewStatesKey());
+
+ final int itemCount = size();
+ for (int i = 0; i < itemCount; i++) {
+ final MenuItem item = getItem(i);
+ final View v = item.getActionView();
+ if (v != null && v.getId() != View.NO_ID) {
+ v.restoreHierarchyState(viewStates);
+ }
+ if (item.hasSubMenu()) {
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ subMenu.restoreActionViewStates(states);
+ }
+ }
+ }
+
+ protected String getActionViewStatesKey() {
+ return ACTION_VIEW_STATES_KEY;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
}
/**
- * Returns the ordering across all items. This will grab the category from
- * the upper bits, find out how to order the category with respect to other
- * categories, and combine it with the lower bits.
- *
- * @param categoryOrder The category order for a particular item (if it has
- * not been or/add with a category, the default category is
- * assumed).
- * @return An ordering integer that can be used to order this item across
- * all the items (even from other categories).
+ * Adds an item to the menu. The other add methods funnel to this.
*/
- private static int getOrdering(int categoryOrder) {
- final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
+ private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
+ final int ordering = getOrdering(categoryOrder);
- if (index < 0 || index >= CATEGORY_TO_ORDER.length) {
- throw new IllegalArgumentException("order does not contain a valid category.");
+ final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
+ ordering, title, mDefaultShowAsAction);
+
+ if (mCurrentMenuInfo != null) {
+ // Pass along the current menu info
+ item.setMenuInfo(mCurrentMenuInfo);
}
- return (CATEGORY_TO_ORDER[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
+ mItems.add(findInsertIndex(mItems, ordering), item);
+ onItemsChanged(true);
+
+ return item;
}
- public void setCallback(Callback callback) {
- mCallback = callback;
+ public MenuItem add(CharSequence title) {
+ return addInternal(0, 0, 0, title);
}
- public Callback getCallback() {
- return mCallback;
+ public MenuItem add(int titleRes) {
+ return addInternal(0, 0, 0, mResources.getString(titleRes));
}
- public boolean getShowsActionItemText() {
- return mShowsActionItemText;
+ public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
+ return addInternal(group, id, categoryOrder, title);
}
- public void setShowsActionItemText(boolean showsActionItemText) {
- mShowsActionItemText = showsActionItemText;
+ public MenuItem add(int group, int id, int categoryOrder, int title) {
+ return addInternal(group, id, categoryOrder, mResources.getString(title));
}
- /**
- * Gets the root menu (if this is a submenu, find its root menu).
- *
- * @return The root menu.
- */
- public MenuBuilder getRootMenu() {
- return this;
+ public SubMenu addSubMenu(CharSequence title) {
+ return addSubMenu(0, 0, 0, title);
+ }
+
+ public SubMenu addSubMenu(int titleRes) {
+ return addSubMenu(0, 0, 0, mResources.getString(titleRes));
+ }
+
+ public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
+ final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
+ final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
+ item.setSubMenu(subMenu);
+
+ return subMenu;
+ }
+
+ public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
+ return addSubMenu(group, id, categoryOrder, mResources.getString(title));
+ }
+
+ public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
+ Intent[] specifics, Intent intent, int flags, android.view.MenuItem[] outSpecificItems) {
+ PackageManager pm = mContext.getPackageManager();
+ final List lri =
+ pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+ final int N = lri != null ? lri.size() : 0;
+
+ if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+ removeGroup(group);
+ }
+
+ for (int i=0; i= 0) {
+ outSpecificItems[ri.specificIndex] = item;
+ }
+ }
+
+ return N;
+ }
+
+ public void removeItem(int id) {
+ removeItemAtInt(findItemIndex(id), true);
+ }
+
+ public void removeGroup(int group) {
+ final int i = findGroupIndex(group);
+
+ if (i >= 0) {
+ final int maxRemovable = mItems.size() - i;
+ int numRemoved = 0;
+ while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
+ // Don't force update for each one, this method will do it at the end
+ removeItemAtInt(i, false);
+ }
+
+ // Notify menu views
+ onItemsChanged(true);
+ }
}
/**
- * Get a list of the items contained in this menu.
+ * Remove the item at the given index and optionally forces menu views to
+ * update.
*
- * @return List of {@link MenuItemImpl}s.
+ * @param index The index of the item to be removed. If this index is
+ * invalid an exception is thrown.
+ * @param updateChildrenOnMenuViews Whether to force update on menu views.
+ * Please make sure you eventually call this after your batch of
+ * removals.
*/
- public final List getItems() {
- return this.mItems;
+ private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
+ if ((index < 0) || (index >= mItems.size())) return;
+
+ mItems.remove(index);
+
+ if (updateChildrenOnMenuViews) onItemsChanged(true);
}
- final MenuItemImpl remove(int index) {
- return this.mItems.remove(index);
+ public void removeItemAt(int index) {
+ removeItemAtInt(index, true);
}
- final Context getContext() {
- return this.mContext;
+ public void clearAll() {
+ mPreventDispatchingItemsChanged = true;
+ clear();
+ clearHeader();
+ mPreventDispatchingItemsChanged = false;
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
+ }
+
+ public void clear() {
+ if (mExpandedItem != null) {
+ collapseItemActionView(mExpandedItem);
+ }
+ mItems.clear();
+
+ onItemsChanged(true);
}
void setExclusiveItemChecked(MenuItem item) {
@@ -218,190 +595,718 @@ public class MenuBuilder implements Menu {
}
}
- // ** Menu Methods ** \\
+ public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
+ final int N = mItems.size();
- @Override
- public MenuItem add(int titleResourceId) {
- return addInternal(0, 0, 0, mContext.getResources().getString(titleResourceId));
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ item.setExclusiveCheckable(exclusive);
+ item.setCheckable(checkable);
+ }
+ }
}
- @Override
- public MenuItem add(int groupId, int itemId, int order, int titleResourceId) {
- return addInternal(itemId, groupId, order, mContext.getResources().getString(titleResourceId));
+ public void setGroupVisible(int group, boolean visible) {
+ final int N = mItems.size();
+
+ // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
+ // than setVisible and at the end notify of items being changed
+
+ boolean changedAtLeastOneItem = false;
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
+ }
+ }
+
+ if (changedAtLeastOneItem) onItemsChanged(true);
}
- @Override
- public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
- return addInternal(itemId, groupId, order, title);
+ public void setGroupEnabled(int group, boolean enabled) {
+ final int N = mItems.size();
+
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getGroupId() == group) {
+ item.setEnabled(enabled);
+ }
+ }
}
- @Override
- public MenuItem add(CharSequence title) {
- return addInternal(0, 0, 0, title);
+ public boolean hasVisibleItems() {
+ final int size = size();
+
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.isVisible()) {
+ return true;
+ }
+ }
+
+ return false;
}
- @Override
- public SubMenuBuilder addSubMenu(CharSequence title) {
- return this.addSubMenu(DEFAULT_GROUP_ID, DEFAULT_ITEM_ID, DEFAULT_ORDER, title);
+ public MenuItem findItem(int id) {
+ final int size = size();
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getItemId() == id) {
+ return item;
+ } else if (item.hasSubMenu()) {
+ MenuItem possibleItem = item.getSubMenu().findItem(id);
+
+ if (possibleItem != null) {
+ return possibleItem;
+ }
+ }
+ }
+
+ return null;
}
- @Override
- public SubMenuBuilder addSubMenu(int titleResourceId) {
- return this.addSubMenu(DEFAULT_GROUP_ID, DEFAULT_ITEM_ID, DEFAULT_ORDER, titleResourceId);
+ public int findItemIndex(int id) {
+ final int size = size();
+
+ for (int i = 0; i < size; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.getItemId() == id) {
+ return i;
+ }
+ }
+
+ return -1;
}
- @Override
- public SubMenuBuilder addSubMenu(int groupId, int itemId, int order, int titleResourceId) {
- String title = this.mContext.getResources().getString(titleResourceId);
- return this.addSubMenu(groupId, itemId, order, title);
+ public int findGroupIndex(int group) {
+ return findGroupIndex(group, 0);
}
- @Override
- public SubMenuBuilder addSubMenu(int groupId, int itemId, int order, CharSequence title) {
- MenuItemImpl item = (MenuItemImpl)this.add(groupId, itemId, order, title);
- SubMenuBuilder subMenu = new SubMenuBuilder(this.mContext, this, item);
- item.setSubMenu(subMenu);
- return subMenu;
+ public int findGroupIndex(int group, int start) {
+ final int size = size();
+
+ if (start < 0) {
+ start = 0;
+ }
+
+ for (int i = start; i < size; i++) {
+ final MenuItemImpl item = mItems.get(i);
+
+ if (item.getGroupId() == group) {
+ return i;
+ }
+ }
+
+ return -1;
}
- @Override
- public void clear() {
- this.mItems.clear();
+ public int size() {
+ return mItems.size();
}
- @Override
- public void close() {}
+ /** {@inheritDoc} */
+ public MenuItem getItem(int index) {
+ return mItems.get(index);
+ }
- @Override
- public MenuItemImpl findItem(int itemId) {
- for (MenuItemImpl item : this.mItems) {
- if (item.getItemId() == itemId) {
- return item;
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ return findItemWithShortcutForKey(keyCode, event) != null;
+ }
+
+ public void setQwertyMode(boolean isQwerty) {
+ mQwertyMode = isQwerty;
+
+ onItemsChanged(false);
+ }
+
+ /**
+ * Returns the ordering across all items. This will grab the category from
+ * the upper bits, find out how to order the category with respect to other
+ * categories, and combine it with the lower bits.
+ *
+ * @param categoryOrder The category order for a particular item (if it has
+ * not been or/add with a category, the default category is
+ * assumed).
+ * @return An ordering integer that can be used to order this item across
+ * all the items (even from other categories).
+ */
+ private static int getOrdering(int categoryOrder) {
+ final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
+
+ if (index < 0 || index >= sCategoryToOrder.length) {
+ throw new IllegalArgumentException("order does not contain a valid category.");
+ }
+
+ return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
+ }
+
+ /**
+ * @return whether the menu shortcuts are in qwerty mode or not
+ */
+ boolean isQwertyMode() {
+ return mQwertyMode;
+ }
+
+ /**
+ * Sets whether the shortcuts should be visible on menus. Devices without hardware
+ * key input will never make shortcuts visible even if this method is passed 'true'.
+ *
+ * @param shortcutsVisible Whether shortcuts should be visible (if true and a
+ * menu item does not have a shortcut defined, that item will
+ * still NOT show a shortcut)
+ */
+ public void setShortcutsVisible(boolean shortcutsVisible) {
+ if (mShortcutsVisible == shortcutsVisible) return;
+
+ setShortcutsVisibleInner(shortcutsVisible);
+ onItemsChanged(false);
+ }
+
+ private void setShortcutsVisibleInner(boolean shortcutsVisible) {
+ mShortcutsVisible = shortcutsVisible
+ && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
+ }
+
+ /**
+ * @return Whether shortcuts should be visible on menus.
+ */
+ public boolean isShortcutsVisible() {
+ return mShortcutsVisible;
+ }
+
+ Resources getResources() {
+ return mResources;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+ }
+
+ /**
+ * Dispatch a mode change event to this menu's callback.
+ */
+ public void changeMenuMode() {
+ if (mCallback != null) {
+ mCallback.onMenuModeChange(this);
+ }
+ }
+
+ private static int findInsertIndex(ArrayList items, int ordering) {
+ for (int i = items.size() - 1; i >= 0; i--) {
+ MenuItemImpl item = items.get(i);
+ if (item.getOrdering() <= ordering) {
+ return i + 1;
}
}
- return null;
+
+ return 0;
}
- @Override
- public MenuItemImpl getItem(int index) {
- return this.mItems.get(index);
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+ final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
+
+ boolean handled = false;
+
+ if (item != null) {
+ handled = performItemAction(item, flags);
+ }
+
+ if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
+ close(true);
+ }
+
+ return handled;
}
- @Override
- public boolean hasVisibleItems() {
- for (MenuItem item : this.mItems) {
- if (item.isVisible()) {
- return true;
+ /*
+ * This function will return all the menu and sub-menu items that can
+ * be directly (the shortcut directly corresponds) and indirectly
+ * (the ALT-enabled char corresponds to the shortcut) associated
+ * with the keyCode.
+ */
+ @SuppressWarnings("deprecation")
+ void findItemsWithShortcutForKey(List items, int keyCode, KeyEvent event) {
+ final boolean qwerty = isQwertyMode();
+ final int metaState = event.getMetaState();
+ final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
+ // Get the chars associated with the keyCode (i.e using any chording combo)
+ final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
+ // The delete key is not mapped to '\b' so we treat it specially
+ if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
+ return;
+ }
+
+ // Look for an item whose shortcut is this key.
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
+ MenuItemImpl item = mItems.get(i);
+ if (item.hasSubMenu()) {
+ ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
+ }
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+ if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
+ (shortcutChar != 0) &&
+ (shortcutChar == possibleChars.meta[0]
+ || shortcutChar == possibleChars.meta[2]
+ || (qwerty && shortcutChar == '\b' &&
+ keyCode == KeyEvent.KEYCODE_DEL)) &&
+ item.isEnabled()) {
+ items.add(item);
}
}
- return false;
}
- @Override
- public void removeItem(int itemId) {
- final int size = this.mItems.size();
+ /*
+ * We want to return the menu item associated with the key, but if there is no
+ * ambiguity (i.e. there is only one menu item corresponding to the key) we want
+ * to return it even if it's not an exact match; this allow the user to
+ * _not_ use the ALT key for example, making the use of shortcuts slightly more
+ * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
+ * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
+ *
+ * On the other hand, if two (or more) shortcuts corresponds to the same key,
+ * we have to only return the exact match.
+ */
+ @SuppressWarnings("deprecation")
+ MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
+ // Get all items that can be associated directly or indirectly with the keyCode
+ ArrayList items = mTempShortcutItemList;
+ items.clear();
+ findItemsWithShortcutForKey(items, keyCode, event);
+
+ if (items.isEmpty()) {
+ return null;
+ }
+
+ final int metaState = event.getMetaState();
+ final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
+ // Get the chars associated with the keyCode (i.e using any chording combo)
+ event.getKeyData(possibleChars);
+
+ // If we have only one element, we can safely returns it
+ final int size = items.size();
+ if (size == 1) {
+ return items.get(0);
+ }
+
+ final boolean qwerty = isQwertyMode();
+ // If we found more than one item associated with the key,
+ // we have to return the exact match
for (int i = 0; i < size; i++) {
- if (this.mItems.get(i).getItemId() == itemId) {
- this.mItems.remove(i);
- return;
+ final MenuItemImpl item = items.get(i);
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
+ if ((shortcutChar == possibleChars.meta[0] &&
+ (metaState & KeyEvent.META_ALT_ON) == 0)
+ || (shortcutChar == possibleChars.meta[2] &&
+ (metaState & KeyEvent.META_ALT_ON) != 0)
+ || (qwerty && shortcutChar == '\b' &&
+ keyCode == KeyEvent.KEYCODE_DEL)) {
+ return item;
}
}
+ return null;
}
- @Override
- public int size() {
- return this.mItems.size();
+ public boolean performIdentifierAction(int id, int flags) {
+ // Look for an item whose identifier is the id.
+ return performItemAction(findItem(id), flags);
}
- @Override
- public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, android.view.MenuItem[] outSpecificItems) {
- PackageManager pm = mContext.getPackageManager();
- final List lri =
- pm.queryIntentActivityOptions(caller, specifics, intent, 0);
- final int N = lri != null ? lri.size() : 0;
+ public boolean performItemAction(MenuItem item, int flags) {
+ MenuItemImpl itemImpl = (MenuItemImpl) item;
- if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
- removeGroup(groupId);
+ if (itemImpl == null || !itemImpl.isEnabled()) {
+ return false;
}
- for (int i=0; i= 0) {
- outSpecificItems[ri.specificIndex] = item;
+ boolean invoked = itemImpl.invoke();
+
+ if (item.hasSubMenu()) {
+ close(false);
+
+ final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ invoked |= dispatchSubMenuSelected(subMenu);
+ if (!invoked) close(true);
+ } else {
+ if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
+ close(true);
}
}
- return N;
+ return invoked;
}
- @Override
- public boolean isShortcutKey(int keyCode, KeyEvent event) {
- return false;
+ /**
+ * Closes the visible menu.
+ *
+ * @param allMenusAreClosing Whether the menus are completely closing (true),
+ * or whether there is another menu coming in this menu's place
+ * (false). For example, if the menu is closing because a
+ * sub menu is about to be shown, allMenusAreClosing
+ * is false.
+ */
+ final void close(boolean allMenusAreClosing) {
+ if (mIsClosing) return;
+
+ mIsClosing = true;
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.onCloseMenu(this, allMenusAreClosing);
+ }
+ }
+ mIsClosing = false;
}
- @Override
- public boolean performIdentifierAction(int id, int flags) {
- throw new RuntimeException("Method not supported.");
+ /** {@inheritDoc} */
+ public void close() {
+ close(true);
}
- @Override
- public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
- return false;
+ /**
+ * Called when an item is added or removed.
+ *
+ * @param structureChanged true if the menu structure changed,
+ * false if only item properties changed.
+ * (Visibility is a structural property since it affects layout.)
+ */
+ void onItemsChanged(boolean structureChanged) {
+ if (!mPreventDispatchingItemsChanged) {
+ if (structureChanged) {
+ mIsVisibleItemsStale = true;
+ mIsActionItemsStale = true;
+ }
+
+ dispatchPresenterUpdate(structureChanged);
+ } else {
+ mItemsChangedWhileDispatchPrevented = true;
+ }
+ }
+
+ /**
+ * Stop dispatching item changed events to presenters until
+ * {@link #startDispatchingItemsChanged()} is called. Useful when
+ * many menu operations are going to be performed as a batch.
+ */
+ public void stopDispatchingItemsChanged() {
+ if (!mPreventDispatchingItemsChanged) {
+ mPreventDispatchingItemsChanged = true;
+ mItemsChangedWhileDispatchPrevented = false;
+ }
+ }
+
+ public void startDispatchingItemsChanged() {
+ mPreventDispatchingItemsChanged = false;
+
+ if (mItemsChangedWhileDispatchPrevented) {
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
+ }
}
- @Override
- public void removeGroup(int groupId) {
- for (int i = mItems.size() - 1; i > 0; i--) {
- if (mItems.get(i).getGroupId() == groupId) {
- mItems.remove(i);
+ /**
+ * Called by {@link MenuItemImpl} when its visible flag is changed.
+ * @param item The item that has gone through a visibility change.
+ */
+ void onItemVisibleChanged(MenuItemImpl item) {
+ // Notify of items being changed
+ mIsVisibleItemsStale = true;
+ onItemsChanged(true);
+ }
+
+ /**
+ * Called by {@link MenuItemImpl} when its action request status is changed.
+ * @param item The item that has gone through a change in action request status.
+ */
+ void onItemActionRequestChanged(MenuItemImpl item) {
+ // Notify of items being changed
+ mIsActionItemsStale = true;
+ onItemsChanged(true);
+ }
+
+ ArrayList getVisibleItems() {
+ if (!mIsVisibleItemsStale) return mVisibleItems;
+
+ // Refresh the visible items
+ mVisibleItems.clear();
+
+ final int itemsSize = mItems.size();
+ MenuItemImpl item;
+ for (int i = 0; i < itemsSize; i++) {
+ item = mItems.get(i);
+ if (item.isVisible()) mVisibleItems.add(item);
+ }
+
+ mIsVisibleItemsStale = false;
+ mIsActionItemsStale = true;
+
+ return mVisibleItems;
+ }
+
+ /**
+ * This method determines which menu items get to be 'action items' that will appear
+ * in an action bar and which items should be 'overflow items' in a secondary menu.
+ * The rules are as follows:
+ *
+ * Items are considered for inclusion in the order specified within the menu.
+ * There is a limit of mMaxActionItems as a total count, optionally including the overflow
+ * menu button itself. This is a soft limit; if an item shares a group ID with an item
+ * previously included as an action item, the new item will stay with its group and become
+ * an action item itself even if it breaks the max item count limit. This is done to
+ * limit the conceptual complexity of the items presented within an action bar. Only a few
+ * unrelated concepts should be presented to the user in this space, and groups are treated
+ * as a single concept.
+ *
+ *
There is also a hard limit of consumed measurable space: mActionWidthLimit. This
+ * limit may be broken by a single item that exceeds the remaining space, but no further
+ * items may be added. If an item that is part of a group cannot fit within the remaining
+ * measured width, the entire group will be demoted to overflow. This is done to ensure room
+ * for navigation and other affordances in the action bar as well as reduce general UI clutter.
+ *
+ *
The space freed by demoting a full group cannot be consumed by future menu items.
+ * Once items begin to overflow, all future items become overflow items as well. This is
+ * to avoid inadvertent reordering that may break the app's intended design.
+ */
+ public void flagActionItems() {
+ if (!mIsActionItemsStale) {
+ return;
+ }
+
+ // Presenters flag action items as needed.
+ boolean flagged = false;
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ flagged |= presenter.flagActionItems();
+ }
+ }
+
+ if (flagged) {
+ mActionItems.clear();
+ mNonActionItems.clear();
+ ArrayList visibleItems = getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.isActionButton()) {
+ mActionItems.add(item);
+ } else {
+ mNonActionItems.add(item);
+ }
}
+ } else {
+ // Nobody flagged anything, everything is a non-action item.
+ // (This happens during a first pass with no action-item presenters.)
+ mActionItems.clear();
+ mNonActionItems.clear();
+ mNonActionItems.addAll(getVisibleItems());
}
+ mIsActionItemsStale = false;
}
- @Override
- public void setGroupCheckable(int groupId, boolean checkable, boolean exclusive) {
- final int N = mItems.size();
- for (int i = 0; i < N; i++) {
- MenuItemImpl item = mItems.get(i);
- if (item.getGroupId() == groupId) {
- item.setExclusiveCheckable(exclusive);
- item.setCheckable(checkable);
+ ArrayList getActionItems() {
+ flagActionItems();
+ return mActionItems;
+ }
+
+ ArrayList getNonActionItems() {
+ flagActionItems();
+ return mNonActionItems;
+ }
+
+ public void clearHeader() {
+ mHeaderIcon = null;
+ mHeaderTitle = null;
+ mHeaderView = null;
+
+ onItemsChanged(false);
+ }
+
+ private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
+ final Drawable icon, final View view) {
+ final Resources r = getResources();
+
+ if (view != null) {
+ mHeaderView = view;
+
+ // If using a custom view, then the title and icon aren't used
+ mHeaderTitle = null;
+ mHeaderIcon = null;
+ } else {
+ if (titleRes > 0) {
+ mHeaderTitle = r.getText(titleRes);
+ } else if (title != null) {
+ mHeaderTitle = title;
+ }
+
+ if (iconRes > 0) {
+ mHeaderIcon = r.getDrawable(iconRes);
+ } else if (icon != null) {
+ mHeaderIcon = icon;
}
+
+ // If using the title or icon, then a custom view isn't used
+ mHeaderView = null;
}
+
+ // Notify of change
+ onItemsChanged(false);
}
- @Override
- public void setGroupEnabled(int groupId, boolean enabled) {
- final int size = this.mItems.size();
- for (int i = 0; i < size; i++) {
- MenuItemImpl item = mItems.get(i);
- if (item.getGroupId() == groupId) {
- item.setEnabled(enabled);
+ /**
+ * Sets the header's title. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param title The new title.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderTitleInt(CharSequence title) {
+ setHeaderInternal(0, title, 0, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's title. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param titleRes The new title (as a resource ID).
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderTitleInt(int titleRes) {
+ setHeaderInternal(titleRes, null, 0, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's icon. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param icon The new icon.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderIconInt(Drawable icon) {
+ setHeaderInternal(0, null, 0, icon, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's icon. This replaces the header view. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param iconRes The new icon (as a resource ID).
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderIconInt(int iconRes) {
+ setHeaderInternal(0, null, iconRes, null, null);
+ return this;
+ }
+
+ /**
+ * Sets the header's view. This replaces the title and icon. Called by the
+ * builder-style methods of subclasses.
+ *
+ * @param view The new view.
+ * @return This MenuBuilder so additional setters can be called.
+ */
+ protected MenuBuilder setHeaderViewInt(View view) {
+ setHeaderInternal(0, null, 0, null, view);
+ return this;
+ }
+
+ public CharSequence getHeaderTitle() {
+ return mHeaderTitle;
+ }
+
+ public Drawable getHeaderIcon() {
+ return mHeaderIcon;
+ }
+
+ public View getHeaderView() {
+ return mHeaderView;
+ }
+
+ /**
+ * Gets the root menu (if this is a submenu, find its root menu).
+ * @return The root menu.
+ */
+ public MenuBuilder getRootMenu() {
+ return this;
+ }
+
+ /**
+ * Sets the current menu info that is set on all items added to this menu
+ * (until this is called again with different menu info, in which case that
+ * one will be added to all subsequent item additions).
+ *
+ * @param menuInfo The extra menu information to add.
+ */
+ public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
+ mCurrentMenuInfo = menuInfo;
+ }
+
+ void setOptionalIconsVisible(boolean visible) {
+ mOptionalIconsVisible = visible;
+ }
+
+ boolean getOptionalIconsVisible() {
+ return mOptionalIconsVisible;
+ }
+
+ public boolean expandItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean expanded = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((expanded = presenter.expandItemActionView(this, item))) {
+ break;
}
}
+ startDispatchingItemsChanged();
+
+ if (expanded) {
+ mExpandedItem = item;
+ }
+ return expanded;
}
- @Override
- public void setGroupVisible(int groupId, boolean visible) {
- final int size = this.mItems.size();
- for (int i = 0; i < size; i++) {
- MenuItemImpl item = mItems.get(i);
- if (item.getGroupId() == groupId) {
- item.setVisible(visible);
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ if (mPresenters.isEmpty() || mExpandedItem != item) return false;
+
+ boolean collapsed = false;
+
+ stopDispatchingItemsChanged();
+ for (WeakReference ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
+ break;
}
}
+ startDispatchingItemsChanged();
+
+ if (collapsed) {
+ mExpandedItem = null;
+ }
+ return collapsed;
}
- @Override
- public void setQwertyMode(boolean isQwerty) {
- throw new RuntimeException("Method not supported.");
+ public MenuItemImpl getExpandedItem() {
+ return mExpandedItem;
}
}
diff --git a/actionbarsherlock/library/src/android/support/v4/view/MenuInflater.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuInflaterImpl.java
similarity index 57%
rename from actionbarsherlock/library/src/android/support/v4/view/MenuInflater.java
rename to actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuInflaterImpl.java
index 4d2a0a2b0..560e551eb 100644
--- a/actionbarsherlock/library/src/android/support/v4/view/MenuInflater.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuInflaterImpl.java
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
- * 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,22 +14,26 @@
* limitations under the License.
*/
-package android.support.v4.view;
+package com.actionbarsherlock.internal.view.menu;
+
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+
import android.content.Context;
import android.content.res.XmlResourceParser;
+import android.support.v4.view.Menu;
+import android.support.v4.view.MenuItem;
+import android.support.v4.view.SubMenu;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Xml;
import android.view.InflateException;
import android.view.View;
-import com.actionbarsherlock.internal.view.menu.MenuBuilder;
-import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
-import com.actionbarsherlock.internal.view.menu.SubMenuBuilder;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
/**
* This class is used to instantiate menu XML files into Menu objects.
@@ -41,13 +44,70 @@ import com.actionbarsherlock.internal.view.menu.SubMenuBuilder;
* it only works with an XmlPullParser returned from a compiled resource (R.
* something file.)
*/
-public final class MenuInflater extends android.view.MenuInflater {
- private static final Class>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] { Context.class };
- private static final Class>[] PARAM_TYPES = new Class[] { android.support.v4.view.MenuItem.class };
-
- /** Android XML namespace. */
+public class MenuInflaterImpl extends android.view.MenuInflater {
+ private static final String LOG_TAG = "MenuInflater";
private static final String XML_NS = "http://schemas.android.com/apk/res/android";
+
+ /*
+ This doesn't work reliably and I can't yet figure out why.
+
+
+ private static final int[] MenuGroup = new int[] {
+ //Ascending order by value
+ android.R.attr.enabled,
+ android.R.attr.id,
+ android.R.attr.visible,
+ android.R.attr.menuCategory,
+ android.R.attr.orderInCategory,
+ android.R.attr.checkableBehavior,
+ };
+ private static final int MenuGroup_enabled = 0;
+ private static final int MenuGroup_id = 1;
+ private static final int MenuGroup_visible = 2;
+ private static final int MenuGroup_menuCategory = 3;
+ private static final int MenuGroup_orderInCategory = 4;
+ private static final int MenuGroup_checkableBehavior = 5;
+
+
+ private static final int[] MenuItem = new int[] {
+ //Ascending order by value
+ android.R.attr.icon,
+ android.R.attr.enabled,
+ android.R.attr.id,
+ android.R.attr.checked,
+ android.R.attr.visible,
+ android.R.attr.menuCategory,
+ android.R.attr.orderInCategory,
+ android.R.attr.title,
+ android.R.attr.titleCondensed,
+ android.R.attr.alphabeticShortcut,
+ android.R.attr.numericShortcut,
+ android.R.attr.checkable,
+ android.R.attr.onClick,
+ android.R.attr.showAsAction,
+ android.R.attr.actionLayout,
+ android.R.attr.actionViewClass,
+ };
+ private static final int MenuItem_icon = 0;
+ private static final int MenuItem_enabled = 1;
+ private static final int MenuItem_id = 2;
+ private static final int MenuItem_checked = 3;
+ private static final int MenuItem_visible = 4;
+ private static final int MenuItem_menuCategory = 5;
+ private static final int MenuItem_orderInCategory = 6;
+ private static final int MenuItem_title = 7;
+ private static final int MenuItem_titleCondensed = 8;
+ private static final int MenuItem_alphabeticShortcut = 9;
+ private static final int MenuItem_numericShortcut = 10;
+ private static final int MenuItem_checkable = 11;
+ private static final int MenuItem_onClick = 12;
+ private static final int MenuItem_showAsAction = 13;
+ private static final int MenuItem_actionLayout = 14;
+ private static final int MenuItem_actionViewClass = 15;
+ */
+
+
/** Menu tag name in XML. */
private static final String XML_MENU = "menu";
@@ -57,26 +117,29 @@ public final class MenuInflater extends android.view.MenuInflater {
/** Item tag name in XML. */
private static final String XML_ITEM = "item";
+ private static final int NO_ID = 0;
+
+ private static final Class>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
+
+ private final Object[] mActionViewConstructorArguments;
- /** Context from which to inflate resources. */
- private final Context mContext;
+ private Context mContext;
/** Native inflater for context menu fallback. */
private final android.view.MenuInflater mNativeMenuInflater;
-
/**
* Constructs a menu inflater.
*
* @see Activity#getMenuInflater()
*/
- public MenuInflater(Context context, android.view.MenuInflater nativeMenuInflater) {
+ public MenuInflaterImpl(Context context, android.view.MenuInflater nativeMenuInflater) {
super(context);
mContext = context;
+ mActionViewConstructorArguments = new Object[] {context};
mNativeMenuInflater = nativeMenuInflater;
}
-
/**
* Inflate a menu hierarchy from the specified XML resource. Throws
* {@link InflateException} if there is an error.
@@ -86,7 +149,6 @@ public final class MenuInflater extends android.view.MenuInflater {
* @param menu The Menu to inflate into. The items and submenus will be
* added to this Menu.
*/
- @Override
public void inflate(int menuRes, android.view.Menu menu) {
if (!(menu instanceof MenuBuilder)) {
mNativeMenuInflater.inflate(menuRes, menu);
@@ -113,9 +175,9 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called internally to fill the given menu. If a sub menu is seen, it will
* call this recursively.
*/
- private void parseMenu(XmlPullParser parser, AttributeSet attrs, MenuBuilder menu)
+ private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
throws XmlPullParserException, IOException {
- ActionBarMenuState menuState = new ActionBarMenuState(menu);
+ MenuState menuState = new MenuState(menu);
int eventType = parser.getEventType();
String tagName;
@@ -152,7 +214,7 @@ public final class MenuInflater extends android.view.MenuInflater {
menuState.readItem(attrs);
} else if (tagName.equals(XML_MENU)) {
// A menu start tag denotes a submenu for an item
- SubMenuBuilder subMenu = menuState.addSubMenuItem();
+ SubMenu subMenu = menuState.addSubMenuItem();
// Parse the submenu into returned SubMenu
parseMenu(parser, attrs, subMenu);
@@ -188,7 +250,40 @@ public final class MenuInflater extends android.view.MenuInflater {
}
}
+ private static class InflatedOnMenuItemClickListener
+ extends MenuItem.OnMenuItemClickListener {
+ private static final Class>[] PARAM_TYPES = new Class[] { MenuItem.class };
+ private Context mContext;
+ private Method mMethod;
+
+ public InflatedOnMenuItemClickListener(Context context, String methodName) {
+ mContext = context;
+ Class> c = context.getClass();
+ try {
+ mMethod = c.getMethod(methodName, PARAM_TYPES);
+ } catch (Exception e) {
+ InflateException ex = new InflateException(
+ "Couldn't resolve menu item onClick handler " + methodName +
+ " in class " + c.getName());
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ try {
+ if (mMethod.getReturnType() == Boolean.TYPE) {
+ return (Boolean) mMethod.invoke(mContext, item);
+ } else {
+ mMethod.invoke(mContext, item);
+ return true;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
/**
* State for the current menu.
@@ -196,8 +291,8 @@ public final class MenuInflater extends android.view.MenuInflater {
* Groups can not be nested unless there is another menu (which will have
* its state class).
*/
- private final class ActionBarMenuState {
- private final MenuBuilder menu;
+ private class MenuState {
+ private Menu menu;
/*
* Group state is set on items as they are added, allowing an item to
@@ -213,8 +308,8 @@ public final class MenuInflater extends android.view.MenuInflater {
private boolean itemAdded;
private int itemId;
private int itemCategoryOrder;
- private String itemTitle;
- private String itemTitleCondensed;
+ private CharSequence itemTitle;
+ private CharSequence itemTitleCondensed;
private int itemIconResId;
private char itemAlphabeticShortcut;
private char itemNumericShortcut;
@@ -228,28 +323,31 @@ public final class MenuInflater extends android.view.MenuInflater {
private boolean itemChecked;
private boolean itemVisible;
private boolean itemEnabled;
- private String itemListenerMethodName;
+
+ /**
+ * Sync to attrs.xml enum, values in MenuItem:
+ * - 0: never
+ * - 1: ifRoom
+ * - 2: always
+ * - -1: Safe sentinel for "no value".
+ */
private int itemShowAsAction;
- private int itemActionLayout;
+
+ private int itemActionViewLayout;
private String itemActionViewClassName;
- private static final int defaultGroupId = View.NO_ID;
- private static final int defaultItemId = View.NO_ID;
+ private String itemListenerMethodName;
+
+ private static final int defaultGroupId = NO_ID;
+ private static final int defaultItemId = NO_ID;
private static final int defaultItemCategory = 0;
private static final int defaultItemOrder = 0;
private static final int defaultItemCheckable = 0;
private static final boolean defaultItemChecked = false;
private static final boolean defaultItemVisible = true;
private static final boolean defaultItemEnabled = true;
- private static final int defaultItemShowAsAction = 0;
- private static final int defaultIconResId = View.NO_ID;
-
- /** Mirror of package-scoped Menu.CATEGORY_MASK. */
- private static final int Menu__CATEGORY_MASK = 0xffff0000;
- /** Mirror of package-scoped Menu.USER_MASK. */
- private static final int Menu__USER_MASK = 0x0000ffff;
- public ActionBarMenuState(MenuBuilder menu) {
+ public MenuState(final Menu menu) {
this.menu = menu;
resetGroup();
@@ -268,24 +366,20 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called when the parser is pointing to a group tag.
*/
public void readGroup(AttributeSet attrs) {
- //TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuGroup);
+ //TypedArray a = mContext.obtainStyledAttributes(attrs,
+ // /*com.android.internal.R.styleable.*/MenuGroup);
- //groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
+ //groupId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuGroup_id, defaultGroupId);
groupId = attrs.getAttributeResourceValue(XML_NS, "id", defaultGroupId);
-
- //groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
+ //groupCategory = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_menuCategory, defaultItemCategory);
groupCategory = attrs.getAttributeIntValue(XML_NS, "menuCategory", defaultItemCategory);
-
- //groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
+ //groupOrder = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_orderInCategory, defaultItemOrder);
groupOrder = attrs.getAttributeIntValue(XML_NS, "orderInCategory", defaultItemOrder);
-
- //groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
+ //groupCheckable = a.getInt(/*com.android.internal.R.styleable.*/MenuGroup_checkableBehavior, defaultItemCheckable);
groupCheckable = attrs.getAttributeIntValue(XML_NS, "checkableBehavior", defaultItemCheckable);
-
- //groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
+ //groupVisible = a.getBoolean(/*com.android.internal.R.styleable.*/MenuGroup_visible, defaultItemVisible);
groupVisible = attrs.getAttributeBooleanValue(XML_NS, "visible", defaultItemVisible);
-
- //groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
+ //groupEnabled = a.getBoolean(/*com.android.internal.R.styleable.*/MenuGroup_enabled, defaultItemEnabled);
groupEnabled = attrs.getAttributeBooleanValue(XML_NS, "enabled", defaultItemEnabled);
//a.recycle();
@@ -295,71 +389,64 @@ public final class MenuInflater extends android.view.MenuInflater {
* Called when the parser is pointing to an item tag.
*/
public void readItem(AttributeSet attrs) {
- //TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuItem);
+ //TypedArray a = mContext.obtainStyledAttributes(attrs,
+ // /*com.android.internal.R.styleable.*/MenuItem);
// Inherit attributes from the group as default value
-
- //itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
+ //itemId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_id, defaultItemId);
itemId = attrs.getAttributeResourceValue(XML_NS, "id", defaultItemId);
-
- //final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
+ //final int category = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_menuCategory, groupCategory);
final int category = attrs.getAttributeIntValue(XML_NS, "menuCategory", groupCategory);
-
- //final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
+ //final int order = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_orderInCategory, groupOrder);
final int order = attrs.getAttributeIntValue(XML_NS, "orderInCategory", groupOrder);
-
- //itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
- itemCategoryOrder = (category & Menu__CATEGORY_MASK) | (order & Menu__USER_MASK);
-
- //itemTitle = a.getString(com.android.internal.R.styleable.MenuItem_title);
+ itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
+ //itemTitle = a.getText(/*com.android.internal.R.styleable.*/MenuItem_title);
final int itemTitleId = attrs.getAttributeResourceValue(XML_NS, "title", 0);
if (itemTitleId != 0) {
itemTitle = mContext.getString(itemTitleId);
} else {
itemTitle = attrs.getAttributeValue(XML_NS, "title");
}
-
- //itemTitleCondensed = a.getString(com.android.internal.R.styleable.MenuItem_titleCondensed);
+ //itemTitleCondensed = a.getText(/*com.android.internal.R.styleable.*/MenuItem_titleCondensed);
final int itemTitleCondensedId = attrs.getAttributeResourceValue(XML_NS, "titleCondensed", 0);
if (itemTitleCondensedId != 0) {
itemTitleCondensed = mContext.getString(itemTitleCondensedId);
} else {
itemTitleCondensed = attrs.getAttributeValue(XML_NS, "titleCondensed");
}
-
- //itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
- itemIconResId = attrs.getAttributeResourceValue(XML_NS, "icon", defaultIconResId);
-
- //itemAlphabeticShortcut = getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
- itemAlphabeticShortcut = getShortcut(attrs.getAttributeValue(XML_NS, "alphabeticShortcut"));
-
- //itemNumericShortcut = getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
- itemNumericShortcut = getShortcut(attrs.getAttributeValue(XML_NS, "numericShortcut"));
-
- //if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
+ //itemIconResId = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_icon, 0);
+ itemIconResId = attrs.getAttributeResourceValue(XML_NS, "icon", 0);
+ //itemAlphabeticShortcut =
+ // getShortcut(a.getString(/*com.android.internal.R.styleable.*/MenuItem_alphabeticShortcut));
+ itemAlphabeticShortcut =
+ getShortcut(attrs.getAttributeValue(XML_NS, "alphabeticShortcut"));
+ //itemNumericShortcut =
+ // getShortcut(a.getString(/*com.android.internal.R.styleable.*/MenuItem_numericShortcut));
+ itemNumericShortcut =
+ getShortcut(attrs.getAttributeValue(XML_NS, "numericShortcut"));
+ //if (a.hasValue(/*com.android.internal.R.styleable.*/MenuItem_checkable)) {
if (attrs.getAttributeValue(XML_NS, "checkable") != null) {
// Item has attribute checkable, use it
- //itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
+ //itemCheckable = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_checkable, false) ? 1 : 0;
itemCheckable = attrs.getAttributeBooleanValue(XML_NS, "checkable", false) ? 1 : 0;
} else {
// Item does not have attribute, use the group's (group can have one more state
// for checkable that represents the exclusive checkable)
itemCheckable = groupCheckable;
}
-
- //itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
+ //itemChecked = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_checked, defaultItemChecked);
itemChecked = attrs.getAttributeBooleanValue(XML_NS, "checked", defaultItemChecked);
-
- //itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
+ //itemVisible = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_visible, groupVisible);
itemVisible = attrs.getAttributeBooleanValue(XML_NS, "visible", groupVisible);
-
- //itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
+ //itemEnabled = a.getBoolean(/*com.android.internal.R.styleable.*/MenuItem_enabled, groupEnabled);
itemEnabled = attrs.getAttributeBooleanValue(XML_NS, "enabled", groupEnabled);
-
- //presumed emulation of 3.0+'s MenuInflator:
+ //itemShowAsAction = a.getInt(/*com.android.internal.R.styleable.*/MenuItem_showAsAction, -1);
+ itemShowAsAction = attrs.getAttributeIntValue(XML_NS, "showAsAction", -1);
+ //itemListenerMethodName = a.getString(/*com.android.internal.R.styleable.*/MenuItem_onClick);
itemListenerMethodName = attrs.getAttributeValue(XML_NS, "onClick");
- itemShowAsAction = attrs.getAttributeIntValue(XML_NS, "showAsAction", defaultItemShowAsAction);
- itemActionLayout = attrs.getAttributeResourceValue(XML_NS, "actionLayout", 0);
+ //itemActionViewLayout = a.getResourceId(/*com.android.internal.R.styleable.*/MenuItem_actionLayout, 0);
+ itemActionViewLayout = attrs.getAttributeResourceValue(XML_NS, "actionLayout", 0);
+ //itemActionViewClassName = a.getString(/*com.android.internal.R.styleable.*/MenuItem_actionViewClass);
itemActionViewClassName = attrs.getAttributeValue(XML_NS, "actionViewClass");
//a.recycle();
@@ -375,7 +462,7 @@ public final class MenuInflater extends android.view.MenuInflater {
}
}
- private void setItem(MenuItemImpl item) {
+ private void setItem(MenuItem item) {
item.setChecked(itemChecked)
.setVisible(itemVisible)
.setEnabled(itemEnabled)
@@ -385,42 +472,52 @@ public final class MenuInflater extends android.view.MenuInflater {
.setAlphabeticShortcut(itemAlphabeticShortcut)
.setNumericShortcut(itemNumericShortcut);
- if (itemShowAsAction > 0) {
+ if (itemShowAsAction >= 0) {
item.setShowAsAction(itemShowAsAction);
}
+
if (itemListenerMethodName != null) {
- if (MenuInflater.this.mContext.isRestricted()) {
- throw new IllegalStateException("The android:onClick attribute cannot be used within a restricted context");
+ if (mContext.isRestricted()) {
+ throw new IllegalStateException("The android:onClick attribute cannot "
+ + "be used within a restricted context");
}
- item.setOnMenuItemClickListener(new InflatedOnMenuItemClickListener(itemListenerMethodName));
+ item.setOnMenuItemClickListener(
+ new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
}
- if (itemCheckable >= 2) {
- item.setExclusiveCheckable(true);
+
+ if (item instanceof MenuItemImpl) {
+ MenuItemImpl impl = (MenuItemImpl) item;
+ if (itemCheckable >= 2) {
+ impl.setExclusiveCheckable(true);
+ }
}
+
+ boolean actionViewSpecified = false;
if (itemActionViewClassName != null) {
- try {
- Context context = MenuInflater.this.mContext;
- ClassLoader loader = context.getClassLoader();
- Class> actionViewClass = Class.forName(itemActionViewClassName, true, loader);
- Constructor> constructor = actionViewClass.getConstructor(ACTION_VIEW_CONSTRUCTOR_SIGNATURE);
- View actionView = (View)constructor.newInstance(new Object[] { context });
- item.setActionView(actionView);
- } catch (Exception e) {
- throw new InflateException(e);
+ View actionView = (View) newInstance(itemActionViewClassName,
+ ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
+ item.setActionView(actionView);
+ actionViewSpecified = true;
+ }
+ if (itemActionViewLayout > 0) {
+ if (!actionViewSpecified) {
+ item.setActionView(itemActionViewLayout);
+ actionViewSpecified = true;
+ } else {
+ Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
+ + " Action view already specified.");
}
- } else if (itemActionLayout > 0) {
- item.setActionView(itemActionLayout);
}
}
public void addItem() {
itemAdded = true;
- setItem((MenuItemImpl)menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
+ setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
}
- public SubMenuBuilder addSubMenuItem() {
+ public SubMenu addSubMenuItem() {
itemAdded = true;
- SubMenuBuilder subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
+ SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(subMenu.getItem());
return subMenu;
}
@@ -428,36 +525,18 @@ public final class MenuInflater extends android.view.MenuInflater {
public boolean hasAddedItem() {
return itemAdded;
}
- }
-
- class InflatedOnMenuItemClickListener extends android.support.v4.view.MenuItem.OnMenuItemClickListener {
- private Method mMethod;
- public InflatedOnMenuItemClickListener(String methodName) {
- final Class> localClass = mContext.getClass();
+ @SuppressWarnings("unchecked")
+ private T newInstance(String className, Class>[] constructorSignature,
+ Object[] arguments) {
try {
- mMethod = localClass.getMethod(methodName, PARAM_TYPES);
+ Class> clazz = mContext.getClassLoader().loadClass(className);
+ Constructor> constructor = clazz.getConstructor(constructorSignature);
+ return (T) constructor.newInstance(arguments);
} catch (Exception e) {
- StringBuilder b = new StringBuilder();
- b.append("Couldn't resolve menu item onClick handler ");
- b.append(methodName);
- b.append(" in class ");
- b.append(localClass.getName());
- throw new InflateException(b.toString(), e);
- }
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- final Object[] params = new Object[] { item };
- try {
- if (mMethod.getReturnType() == Boolean.TYPE) {
- return (Boolean)mMethod.invoke(mContext, params);
- }
- return false;
- } catch (Exception e) {
- throw new RuntimeException(e);
+ Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
}
+ return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java
index 9e2b7c704..a2b1d24f0 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuItemImpl.java
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
- * 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,128 +16,131 @@
package com.actionbarsherlock.internal.view.menu;
-import java.lang.ref.WeakReference;
-import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
-import android.content.DialogInterface;
+import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MenuItem;
+import android.support.v4.view.SubMenu;
import android.util.Log;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewDebug;
+import android.widget.LinearLayout;
/**
- * An implementation of the {@link android.view.MenuItem} interface for use in
- * inflating menu XML resources to be added to a third-party action bar.
- *
- * @author Jake Wharton
- * @see com.android.internal.view.menu.MenuItemImpl
+ * @hide
*/
public final class MenuItemImpl implements MenuItem {
private static final String TAG = "MenuItemImpl";
- private final MenuBuilder mMenu;
+ private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
+ SHOW_AS_ACTION_IF_ROOM |
+ SHOW_AS_ACTION_ALWAYS;
- private final int mItemId;
- private final int mGroupId;
+ private final int mId;
+ private final int mGroup;
private final int mCategoryOrder;
private final int mOrdering;
-
- private Intent mIntent;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
- private char mNumericalShortcut;
- private char mAlphabeticalShortcut;
- private int mShowAsAction;
+ private Intent mIntent;
+ private char mShortcutNumericChar;
+ private char mShortcutAlphabeticChar;
+
+ /** The icon's drawable which is only created as needed */
+ private Drawable mIconDrawable;
+ /**
+ * The icon's resource ID which is used to get the Drawable when it is
+ * needed (if the Drawable isn't already obtained--only one of the two is
+ * needed).
+ */
+ private int mIconResId = NO_ICON;
+
+ /** The menu to which this item belongs */
+ private MenuBuilder mMenu;
+ /** If this item should launch a sub menu, this is the sub menu to launch */
private SubMenuBuilder mSubMenu;
+
private Runnable mItemCallback;
- private OnMenuItemClickListener mClickListener;
- private Drawable mIcon;
- private int mIconRes = View.NO_ID;
+ private MenuItem.OnMenuItemClickListener mClickListener;
+
+ private int mFlags = ENABLED;
+ private static final int CHECKABLE = 0x00000001;
+ private static final int CHECKED = 0x00000002;
+ private static final int EXCLUSIVE = 0x00000004;
+ private static final int HIDDEN = 0x00000008;
+ private static final int ENABLED = 0x00000010;
+ private static final int IS_ACTION = 0x00000020;
+
+ private int mShowAsAction = SHOW_AS_ACTION_NEVER;
+
private View mActionView;
- private int mActionViewRes = View.NO_ID;
-
- int mFlags = ENABLED;
- static final int CHECKABLE = 0x01;
- static final int CHECKED = 0x02;
- static final int EXCLUSIVE = 0x04;
- static final int HIDDEN = 0x08;
- static final int ENABLED = 0x10;
- static final int IS_ACTION = 0x20;
-
- private final WeakReference[] mItemViews;
-
- private final DialogInterface.OnClickListener subMenuClick = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int index) {
- dialog.dismiss();
- mSubMenu.getItem(index).invoke();
- }
- };
- private final DialogInterface.OnMultiChoiceClickListener subMenuMultiClick = new DialogInterface.OnMultiChoiceClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int index, boolean isChecked) {
- dialog.dismiss();
- mSubMenu.getItem(index).setChecked(isChecked);
- }
- };
+
+ /** Used for the icon resource ID if this item does not have an icon */
+ static final int NO_ICON = 0;
+
+ /**
+ * Current use case is for context menu: Extra information linked to the
+ * View that added this item to the context menu.
+ */
+ private ContextMenuInfo mMenuInfo;
+
+ private static String sPrependShortcutLabel;
+ private static String sEnterShortcutLabel;
+ private static String sDeleteShortcutLabel;
+ private static String sSpaceShortcutLabel;
/**
- * Create a new action bar menu item.
+ * Instantiates this menu item.
*
- * @param context Context used if resource resolution is required.
- * @param itemId A unique ID. Used in the activity callback.
- * @param groupId Group ID. Currently unused.
- * @param order Item order. Currently unused.
- * @param title Title of the item.
+ * @param menu
+ * @param group Item ordering grouping control. The item will be added after
+ * all other items whose order is <= this number, and before any
+ * that are larger than it. This can also be used to define
+ * groups of items for batch state changes. Normally use 0.
+ * @param id Unique item ID. Use 0 if you do not need a unique ID.
+ * @param categoryOrder The ordering for this item.
+ * @param title The text to display for the item.
*/
- @SuppressWarnings("unchecked")
- public MenuItemImpl(MenuBuilder menu, int groupId, int itemId, int order, int ordering, CharSequence title, int showAsAction) {
- mMenu = menu;
+ MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+ CharSequence title, int showAsAction) {
+
+ /* TODO if (sPrependShortcutLabel == null) {
+ // This is instantiated from the UI thread, so no chance of sync issues
+ sPrependShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.prepend_shortcut_label);
+ sEnterShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_enter_shortcut_label);
+ sDeleteShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_delete_shortcut_label);
+ sSpaceShortcutLabel = menu.getContext().getResources().getString(
+ com.android.internal.R.string.menu_space_shortcut_label);
+ }*/
- mItemId = itemId;
- mGroupId = groupId;
- mCategoryOrder = order;
+ mMenu = menu;
+ mId = id;
+ mGroup = group;
+ mCategoryOrder = categoryOrder;
mOrdering = ordering;
mTitle = title;
mShowAsAction = showAsAction;
-
- mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
}
-
-
+ /**
+ * Invokes the item by calling various listeners or callbacks.
+ *
+ * @return true if the invocation was handled, false otherwise
+ */
public boolean invoke() {
- if (hasSubMenu()) {
- AlertDialog.Builder builder = new AlertDialog.Builder(mMenu.getContext());
- builder.setTitle(getTitle());
-
- final boolean isExclusive = mSubMenu.getItem(0).isExclusiveCheckable();
- final boolean isCheckable = mSubMenu.getItem(0).isCheckable();
- final CharSequence[] titles = getSubMenuTitles();
- if (isExclusive) {
- builder.setSingleChoiceItems(titles, getSubMenuSelected(), subMenuClick);
- } else if (isCheckable) {
- builder.setMultiChoiceItems(titles, getSubMenuChecked(), subMenuMultiClick);
- } else {
- builder.setItems(titles, subMenuClick);
- }
-
- builder.show();
- return true;
- }
-
if (mClickListener != null &&
mClickListener.onMenuItemClick(this)) {
return true;
}
- MenuBuilder.Callback callback = mMenu.getRootMenu().getCallback();
- if (callback != null &&
- callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+ if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
@@ -159,505 +161,416 @@ public final class MenuItemImpl implements MenuItem {
return false;
}
- private CharSequence[] getSubMenuTitles() {
- final int count = mSubMenu.size();
- CharSequence[] list = new CharSequence[count];
- for (int i = 0; i < count; i++) {
- list[i] = mSubMenu.getItem(i).getTitle();
- }
- return list;
+ public boolean isEnabled() {
+ return (mFlags & ENABLED) != 0;
}
- private int getSubMenuSelected() {
- final int count = mSubMenu.size();
- for (int i = 0; i < count; i++) {
- if (mSubMenu.getItem(i).isChecked()) {
- return i;
- }
+ public MenuItem setEnabled(boolean enabled) {
+ if (enabled) {
+ mFlags |= ENABLED;
+ } else {
+ mFlags &= ~ENABLED;
}
- return -1;
- }
- private boolean[] getSubMenuChecked() {
- final int count = mSubMenu.size();
- boolean[] checked = new boolean[count];
- for (int i = 0; i < count; i++) {
- checked[i] = mSubMenu.getItem(i).isChecked();
- }
- return checked;
- }
+ mMenu.onItemsChanged(false);
- private boolean hasItemView(int menuType) {
- return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
+ return this;
}
- public void setItemView(int type, MenuView.ItemView itemView) {
- mItemViews[type] = new WeakReference(itemView);
+ public int getGroupId() {
+ return mGroup;
}
-
- public void addTo(android.view.Menu menu) {
- if (hasSubMenu()) {
- android.view.SubMenu subMenu = menu.addSubMenu(mGroupId, mItemId, mCategoryOrder, mTitle);
- if (mIconRes != View.NO_ID) {
- subMenu.setIcon(mIconRes);
- } else {
- subMenu.setIcon(mIcon);
- }
- for (MenuItemImpl item : mSubMenu.getItems()) {
- item.addTo(subMenu);
- }
-
- if (mSubMenu.getItem(0).isExclusiveCheckable()) {
- int checked = getSubMenuSelected();
- if (checked != -1) {
- subMenu.getItem(checked).setChecked(true);
- }
- }
- } else {
- android.view.MenuItem item = menu.add(mGroupId, mItemId, mCategoryOrder, mTitle)
- .setAlphabeticShortcut(mAlphabeticalShortcut)
- .setNumericShortcut(mNumericalShortcut)
- .setVisible(isVisible())
- .setIntent(mIntent)
- .setCheckable(isCheckable())
- .setChecked(isChecked())
- .setOnMenuItemClickListener(mClickListener);
-
- if (isExclusiveCheckable()) {
- menu.setGroupCheckable(mGroupId, true, true);
- }
-
- //Create and initialize a native itemview wrapper
- NativeMenuItemView nativeWrapper = new NativeMenuItemView(item);
- nativeWrapper.initialize(this, MenuBuilder.TYPE_NATIVE);
-
- //Associate the itemview to this so changes will be reflected
- setItemView(MenuBuilder.TYPE_NATIVE, nativeWrapper);
- }
+ @ViewDebug.CapturedViewProperty
+ public int getItemId() {
+ return mId;
}
- /**
- * Get whether or not this item is being shown on the action bar.
- *
- * @return {@code true} if shown, {@code false} otherwise.
- */
- public boolean isShownOnActionBar() {
- return (mFlags & IS_ACTION) == IS_ACTION;
+ public int getOrder() {
+ return mCategoryOrder;
}
- /**
- * Denote whether or not this menu item is being shown on the action bar.
- *
- * @param isShownOnActionBar {@code true} if shown or {@code false}.
- */
- public void setIsShownOnActionBar(boolean isShownOnActionBar) {
- mFlags = (mFlags & ~IS_ACTION) | (isShownOnActionBar ? IS_ACTION : 0);
+ public int getOrdering() {
+ return mOrdering;
}
- @Override
public Intent getIntent() {
- return this.mIntent;
+ return mIntent;
}
- @Override
- public int getItemId() {
- return this.mItemId;
+ public MenuItem setIntent(Intent intent) {
+ mIntent = intent;
+ return this;
}
- @Override
- public CharSequence getTitle() {
- return this.mTitle;
+ Runnable getCallback() {
+ return mItemCallback;
}
- @Override
- public boolean isEnabled() {
- return (mFlags & ENABLED) != 0;
+ public MenuItem setCallback(Runnable callback) {
+ mItemCallback = callback;
+ return this;
}
- @Override
- public boolean isVisible() {
- return (mFlags & HIDDEN) == 0;
+ public char getAlphabeticShortcut() {
+ return mShortcutAlphabeticChar;
}
- @Override
- public MenuItem setEnabled(boolean enabled) {
- final boolean oldValue = isEnabled();
- mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
-
- if (oldValue != enabled) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setEnabled(enabled);
- }
- }
- }
-
- return this;
- }
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ if (mShortcutAlphabeticChar == alphaChar) return this;
- @Override
- public MenuItem setIcon(int iconResourceId) {
- mIcon = null;
- mIconRes = iconResourceId;
+ mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- if (mIconRes != View.NO_ID) {
- setIconOnViews(mMenu.getContext().getResources().getDrawable(mIconRes));
- }
+ mMenu.onItemsChanged(false);
return this;
}
- @Override
- public MenuItem setIntent(Intent intent) {
- mIntent = intent;
- return this;
+ public char getNumericShortcut() {
+ return mShortcutNumericChar;
}
- @Override
- public MenuItem setTitle(CharSequence title) {
- mTitle = title;
- return this;
- }
+ public MenuItem setNumericShortcut(char numericChar) {
+ if (mShortcutNumericChar == numericChar) return this;
- @Override
- public MenuItem setTitle(int titleResourceId) {
- mTitle = mMenu.getContext().getResources().getString(titleResourceId);
- return this;
- }
+ mShortcutNumericChar = numericChar;
+
+ mMenu.onItemsChanged(false);
- @Override
- public MenuItem setVisible(boolean visible) {
- final boolean oldValue = isVisible();
- mFlags = (mFlags & ~HIDDEN) | (visible ? 0 : HIDDEN);
- if (oldValue != visible) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setVisible(visible);
- }
- }
- }
return this;
}
- @Override
- public boolean isChecked() {
- return (mFlags & CHECKED) == CHECKED;
- }
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ mShortcutNumericChar = numericChar;
+ mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- @Override
- public MenuItem setChecked(boolean checked) {
- if ((mFlags & EXCLUSIVE) == EXCLUSIVE) {
- // Call the method on the Menu since it knows about the others in this
- // exclusive checkable group
- mMenu.setExclusiveItemChecked(this);
- } else {
- setCheckedInt(checked);
- }
+ mMenu.onItemsChanged(false);
return this;
}
- void setCheckedInt(boolean checked) {
- final boolean oldValue = isChecked();
- mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
- if (oldValue != checked) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setChecked(checked);
- }
- }
- }
+ /**
+ * @return The active shortcut (based on QWERTY-mode of the menu).
+ */
+ char getShortcut() {
+ return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
}
- @Override
- public boolean isCheckable() {
- return (mFlags & CHECKABLE) == CHECKABLE;
- }
+ /**
+ * @return The label to show for the shortcut. This includes the chording
+ * key (for example 'Menu+a'). Also, any non-human readable
+ * characters should be human readable (for example 'Menu+enter').
+ */
+ String getShortcutLabel() {
- @Override
- public MenuItem setCheckable(boolean checkable) {
- final boolean oldValue = isCheckable();
- mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
- if (oldValue != checkable) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setCheckable(checkable);
- }
- }
+ char shortcut = getShortcut();
+ if (shortcut == 0) {
+ return "";
}
- return this;
- }
+ StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
+ switch (shortcut) {
- public void setExclusiveCheckable(boolean exclusive) {
- mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
- }
+ case '\n':
+ sb.append(sEnterShortcutLabel);
+ break;
- public boolean isExclusiveCheckable() {
- return (mFlags & EXCLUSIVE) == EXCLUSIVE;
- }
+ case '\b':
+ sb.append(sDeleteShortcutLabel);
+ break;
- @Override
- public CharSequence getTitleCondensed() {
- return mTitleCondensed;
+ case ' ':
+ sb.append(sSpaceShortcutLabel);
+ break;
+
+ default:
+ sb.append(shortcut);
+ break;
+ }
+
+ return sb.toString();
}
- @Override
- public MenuItem setTitleCondensed(CharSequence title) {
- mTitleCondensed = title;
- return this;
+ /**
+ * @return Whether this menu item should be showing shortcuts (depends on
+ * whether the menu should show shortcuts and whether this item has
+ * a shortcut defined)
+ */
+ boolean shouldShowShortcut() {
+ // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
+ return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
- @Override
- public int getGroupId() {
- return mGroupId;
+ public SubMenu getSubMenu() {
+ return mSubMenu;
}
- @Override
- public int getOrder() {
- return mCategoryOrder;
+ public boolean hasSubMenu() {
+ return mSubMenu != null;
}
- public int getOrdering() {
- return mOrdering;
+ void setSubMenu(SubMenuBuilder subMenu) {
+ mSubMenu = subMenu;
+
+ subMenu.setHeaderTitle(getTitle());
}
- @Override
- public SubMenuBuilder getSubMenu() {
- return mSubMenu;
+ @ViewDebug.CapturedViewProperty
+ public CharSequence getTitle() {
+ return mTitle;
}
/**
- * Set the sub-menu of this item.
+ * Gets the title for a particular {@link ItemView}
*
- * @param subMenu Sub-menu instance.
- * @return This Item so additional setters can be called.
+ * @param itemView The ItemView that is receiving the title
+ * @return Either the title or condensed title based on what the ItemView
+ * prefers
*/
- MenuItem setSubMenu(SubMenuBuilder subMenu) {
- mSubMenu = subMenu;
- return this;
+ CharSequence getTitleForItemView(MenuView.ItemView itemView) {
+ return ((itemView != null) && itemView.prefersCondensedTitle())
+ ? getTitleCondensed()
+ : getTitle();
}
- @Override
- public boolean hasSubMenu() {
- return (mSubMenu != null) && (mSubMenu.size() > 0);
- }
+ public MenuItem setTitle(CharSequence title) {
+ mTitle = title;
- @Override
- public char getAlphabeticShortcut() {
- return mAlphabeticalShortcut;
- }
+ mMenu.onItemsChanged(false);
- @Override
- public char getNumericShortcut() {
- return mNumericalShortcut;
- }
+ if (mSubMenu != null) {
+ mSubMenu.setHeaderTitle(title);
+ }
- @Override
- public MenuItem setAlphabeticShortcut(char alphaChar) {
- mAlphabeticalShortcut = Character.toLowerCase(alphaChar);
return this;
}
- @Override
- public MenuItem setNumericShortcut(char numericChar) {
- mNumericalShortcut = numericChar;
- return this;
+ public MenuItem setTitle(int title) {
+ return setTitle(mMenu.getContext().getString(title));
}
- @Override
- public MenuItem setShortcut(char numericChar, char alphaChar) {
- setNumericShortcut(numericChar);
- setAlphabeticShortcut(alphaChar);
- return this;
+ public CharSequence getTitleCondensed() {
+ return mTitleCondensed != null ? mTitleCondensed : mTitle;
}
- @Override
- public void setShowAsAction(int actionEnum) {
- mShowAsAction = actionEnum;
- }
+ public MenuItem setTitleCondensed(CharSequence title) {
+ mTitleCondensed = title;
- public int getShowAsAction() {
- return mShowAsAction;
- }
+ // Could use getTitle() in the loop below, but just cache what it would do here
+ if (title == null) {
+ title = mTitle;
+ }
- public boolean showsActionItemText() {
- return mMenu.getShowsActionItemText();
- }
+ mMenu.onItemsChanged(false);
- @Override
- public View getActionView() {
- if (mActionView != null) {
- return mActionView;
- }
- if (mActionViewRes != View.NO_ID) {
- return LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false);
- }
- return null;
+ return this;
}
- @Override
public Drawable getIcon() {
- if (mIcon != null) {
- return mIcon;
+ if (mIconDrawable != null) {
+ return mIconDrawable;
}
- if (mIconRes != View.NO_ID) {
- return mMenu.getContext().getResources().getDrawable(mIconRes);
+
+ if (mIconResId != NO_ICON) {
+ return mMenu.getResources().getDrawable(mIconResId);
}
+
return null;
}
- @Override
- public ContextMenuInfo getMenuInfo() {
- return null;
+ public MenuItem setIcon(Drawable icon) {
+ mIconResId = NO_ICON;
+ mIconDrawable = icon;
+ mMenu.onItemsChanged(false);
+
+ return this;
}
- @Override
- public MenuItem setActionView(View view) {
- mActionView = view;
- mActionViewRes = View.NO_ID;
- setActionViewOnViews(mActionView);
+ public MenuItem setIcon(int iconResId) {
+ mIconDrawable = null;
+ mIconResId = iconResId;
+
+ // If we have a view, we need to push the Drawable to them
+ mMenu.onItemsChanged(false);
+
return this;
}
- @Override
- public MenuItem setActionView(int resId) {
- mActionView = null;
- mActionViewRes = resId;
+ public boolean isCheckable() {
+ return (mFlags & CHECKABLE) == CHECKABLE;
+ }
- if (mActionViewRes != View.NO_ID) {
- setActionViewOnViews(LayoutInflater.from(mMenu.getContext()).inflate(mActionViewRes, null, false));
+ public MenuItem setCheckable(boolean checkable) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+ if (oldFlags != mFlags) {
+ mMenu.onItemsChanged(false);
}
return this;
}
- void setActionViewOnViews(View view) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setActionView(view);
- }
- }
+ public void setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
- @Override
- public MenuItem setIcon(Drawable icon) {
- mIcon = icon;
- mIconRes = View.NO_ID;
- setIconOnViews(icon);
- return this;
+ public boolean isExclusiveCheckable() {
+ return (mFlags & EXCLUSIVE) != 0;
}
- void setIconOnViews(Drawable icon) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setIcon(icon);
- }
+ public boolean isChecked() {
+ return (mFlags & CHECKED) == CHECKED;
+ }
+
+ public MenuItem setChecked(boolean checked) {
+ if ((mFlags & EXCLUSIVE) != 0) {
+ // Call the method on the Menu since it knows about the others in this
+ // exclusive checkable group
+ mMenu.setExclusiveItemChecked(this);
+ } else {
+ setCheckedInt(checked);
}
+
+ return this;
}
- @Override
- public android.view.MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener menuItemClickListener) {
- return this.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return menuItemClickListener.onMenuItemClick(new MenuItemWrapper(item));
- }
- });
+ void setCheckedInt(boolean checked) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+ if (oldFlags != mFlags) {
+ mMenu.onItemsChanged(false);
+ }
}
- @Override
- public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
- mClickListener = menuItemClickListener;
- return this;
+ public boolean isVisible() {
+ return (mFlags & HIDDEN) == 0;
}
/**
- * Returns the currently set menu click listener for this item.
+ * Changes the visibility of the item. This method DOES NOT notify the
+ * parent menu of a change in this item, so this should only be called from
+ * methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)}
+ * instead.
*
- * @return Click listener or {@code null}.
+ * @param shown Whether to show (true) or hide (false).
+ * @return Whether the item's shown state was changed
*/
- public OnMenuItemClickListener getOnMenuItemClickListener() {
- return mClickListener;
+ boolean setVisibleInt(boolean shown) {
+ final int oldFlags = mFlags;
+ mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
+ return oldFlags != mFlags;
}
+ public MenuItem setVisible(boolean shown) {
+ // Try to set the shown state to the given state. If the shown state was changed
+ // (i.e. the previous state isn't the same as given state), notify the parent menu that
+ // the shown state has changed for this item
+ if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
+ return this;
+ }
+ public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
+ mClickListener = clickListener;
+ return this;
+ }
- public static final class NativeMenuItemView implements MenuView.ItemView {
- private final android.view.MenuItem mItem;
-
+ public MenuItem setOnMenuItemClickListener(final android.view.MenuItem.OnMenuItemClickListener clickListener) {
+ mClickListener = new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return clickListener.onMenuItemClick(item);
+ }
+ };
+ return this;
+ }
- public NativeMenuItemView(android.view.MenuItem item) {
- mItem = item;
- }
+ @Override
+ public String toString() {
+ return mTitle.toString();
+ }
+ void setMenuInfo(ContextMenuInfo menuInfo) {
+ mMenuInfo = menuInfo;
+ }
- @Override
- public MenuItemImpl getItemData() {
- return null;
- }
+ public ContextMenuInfo getMenuInfo() {
+ return mMenuInfo;
+ }
- @Override
- public void initialize(MenuItemImpl itemData, int menuType) {
- setIcon(itemData.getIcon());
- setTitle(itemData.getTitle());
- setEnabled(itemData.isEnabled());
- setCheckable(itemData.isCheckable());
- setChecked(itemData.isChecked());
- setActionView(itemData.getActionView());
- setVisible(itemData.isVisible());
- }
+ public void actionFormatChanged() {
+ mMenu.onItemActionRequestChanged(this);
+ }
- @Override
- public boolean prefersCondensedTitle() {
- return true;
- }
+ /**
+ * @return Whether the menu should show icons for menu items.
+ */
+ public boolean shouldShowIcon() {
+ return mMenu.getOptionalIconsVisible();
+ }
- @Override
- public void setCheckable(boolean checkable) {
- mItem.setCheckable(checkable);
- }
+ public boolean isActionButton() {
+ return (mFlags & IS_ACTION) == IS_ACTION;
+ }
- @Override
- public void setChecked(boolean checked) {
- mItem.setChecked(checked);
- }
+ public boolean requestsActionButton() {
+ return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
+ }
- @Override
- public void setEnabled(boolean enabled) {
- mItem.setEnabled(enabled);
- }
+ public boolean requiresActionButton() {
+ return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
+ }
- @Override
- public void setIcon(Drawable icon) {
- mItem.setIcon(icon);
+ public void setIsActionButton(boolean isActionButton) {
+ if (isActionButton) {
+ mFlags |= IS_ACTION;
+ } else {
+ mFlags &= ~IS_ACTION;
}
+ }
- @Override
- public void setShortcut(boolean showShortcut, char shortcutKey) {
- //Not supported
- }
+ public boolean showsTextAsAction() {
+ return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
+ }
- @Override
- public void setTitle(CharSequence title) {
- mItem.setTitle(title);
+ public void setShowAsAction(int actionEnum) {
+ switch (actionEnum & SHOW_AS_ACTION_MASK) {
+ case SHOW_AS_ACTION_ALWAYS:
+ case SHOW_AS_ACTION_IF_ROOM:
+ case SHOW_AS_ACTION_NEVER:
+ // Looks good!
+ break;
+
+ default:
+ // Mutually exclusive options selected!
+ throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
+ + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
}
+ mShowAsAction = actionEnum;
+ mMenu.onItemActionRequestChanged(this);
+ }
- @Override
- public boolean showsIcon() {
- return true;
+ public MenuItem setActionView(View view) {
+ mActionView = view;
+ if (view != null && view.getId() == View.NO_ID && mId > 0) {
+ view.setId(mId);
}
+ mMenu.onItemActionRequestChanged(this);
+ return this;
+ }
- @Override
- public void setActionView(View actionView) {
- //Not supported
- }
+ public MenuItem setActionView(int resId) {
+ final Context context = mMenu.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ setActionView(inflater.inflate(resId, new LinearLayout(context), false));
+ return this;
+ }
- @Override
- public void setVisible(boolean visible) {
- mItem.setVisible(visible);
+ public View getActionView() {
+ if (mActionView != null) {
+ return mActionView;
+ } else {
+ return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
new file mode 100644
index 000000000..c3f35472c
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+
+ /**
+ * Called when a menu item with a collapsable action view should expand its action view.
+ *
+ * @param menu Menu containing the item to be expanded
+ * @param item Item to be expanded
+ * @return true if this presenter expanded the action view, false otherwise.
+ */
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Called when a menu item with a collapsable action view should collapse its action view.
+ *
+ * @param menu Menu containing the item to be collapsed
+ * @param item Item to be collapsed
+ * @return true if this presenter collapsed the action view, false otherwise.
+ */
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Returns an ID for determining how to save/restore instance state.
+ * @return a valid ID value.
+ */
+ public int getId();
+
+ /**
+ * Returns a Parcelable describing the current state of the presenter.
+ * It will be passed to the {@link #onRestoreInstanceState(Parcelable)}
+ * method of the presenter sharing the same ID later.
+ * @return The saved instance state
+ */
+ public Parcelable onSaveInstanceState();
+
+ /**
+ * Supplies the previously saved instance state to be restored.
+ * @param state The previously saved instance state
+ */
+ public void onRestoreInstanceState(Parcelable state);
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuView.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuView.java
index daae3fe8c..323ba2d88 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuView.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/MenuView.java
@@ -17,10 +17,9 @@
package com.actionbarsherlock.internal.view.menu;
import android.graphics.drawable.Drawable;
-import android.view.View;
/**
- * Minimal interface for a menu view. {@link #initialize(MenuBuilder, int)} must be called for the
+ * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
* menu to be functional.
*
* @hide
@@ -31,18 +30,8 @@ public interface MenuView {
* view is inflated.
*
* @param menu The menu that this MenuView should display.
- * @param menuType The type of this menu, one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_DIALOG}).
*/
- public void initialize(MenuBuilder menu, int menuType);
-
- /**
- * Forces the menu view to update its view to reflect the new state of the menu.
- *
- * @param cleared Whether the menu was cleared or just modified.
- */
- public void updateChildren(boolean cleared);
+ public void initialize(MenuBuilder menu);
/**
* Returns the default animations to be used for this menu when entering/exiting.
@@ -97,12 +86,6 @@ public interface MenuView {
*/
public void setChecked(boolean checked);
- /**
- * Sets the visibility for the item view.
- * @param visible Whether the item is visible
- */
- public void setVisible(boolean visible);
-
/**
* Sets the shortcut for the item.
* @param showShortcut Whether a shortcut should be shown(if false, the value of
@@ -117,12 +100,6 @@ public interface MenuView {
*/
public void setIcon(Drawable icon);
- /**
- * Set the action view of this item view.
- * @param actionView Action view.
- */
- public void setActionView(View actionView);
-
/**
* Whether this item view prefers displaying the condensed title rather
* than the normal title. If a condensed title is not available, the
@@ -140,4 +117,4 @@ public interface MenuView {
*/
public boolean showsIcon();
}
-}
\ No newline at end of file
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java
index bf73ed757..3eefbcad2 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/view/menu/SubMenuBuilder.java
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
- * Copyright (C) 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +18,16 @@ package com.actionbarsherlock.internal.view.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.support.v4.view.Menu;
+import android.support.v4.view.MenuItem;
import android.support.v4.view.SubMenu;
import android.view.View;
/**
- * The model for a sub menu, which is an extension of the menu. Most methods
- * are proxied to the parent menu.
+ * The model for a sub menu, which is an extension of the menu. Most methods are proxied to
+ * the parent menu.
*/
-public final class SubMenuBuilder extends MenuBuilder implements SubMenu {
+public class SubMenuBuilder extends MenuBuilder implements SubMenu {
private MenuBuilder mParentMenu;
private MenuItemImpl mItem;
@@ -42,78 +43,91 @@ public final class SubMenuBuilder extends MenuBuilder implements SubMenu {
mParentMenu.setQwertyMode(isQwerty);
}
- //@Override
- //public boolean isQwertyMode() {
- // return mParentMenu.isQwertyMode();
- //}
+ @Override
+ public boolean isQwertyMode() {
+ return mParentMenu.isQwertyMode();
+ }
- //@Override
- //public void setShortcutsVisible(boolean shortcutsVisible) {
- // mParentMenu.setShortcutsVisible(shortcutsVisible);
- //}
+ @Override
+ public void setShortcutsVisible(boolean shortcutsVisible) {
+ mParentMenu.setShortcutsVisible(shortcutsVisible);
+ }
- //@Override
- //public boolean isShortcutsVisible() {
- // return mParentMenu.isShortcutsVisible();
- //}
+ @Override
+ public boolean isShortcutsVisible() {
+ return mParentMenu.isShortcutsVisible();
+ }
- MenuBuilder getParentMenu() {
+ public Menu getParentMenu() {
return mParentMenu;
}
- @Override
- public MenuItemImpl getItem() {
+ public MenuItem getItem() {
return mItem;
}
- //@Override
- //public Callback getCallback() {
- // return mParentMenu.getCallback();
- //}
-
- //@Override
- //public void setCallback(Callback callback) {
- // mParentMenu.setCallback(callback);
- //}
+ @Override
+ public void setCallback(Callback callback) {
+ mParentMenu.setCallback(callback);
+ }
@Override
public MenuBuilder getRootMenu() {
return mParentMenu;
}
- public SubMenuBuilder setIcon(Drawable icon) {
+ @Override
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return super.dispatchMenuItemSelected(menu, item) ||
+ mParentMenu.dispatchMenuItemSelected(menu, item);
+ }
+
+ public SubMenu setIcon(Drawable icon) {
mItem.setIcon(icon);
return this;
}
- public SubMenuBuilder setIcon(int iconRes) {
+ public SubMenu setIcon(int iconRes) {
mItem.setIcon(iconRes);
return this;
}
- public SubMenuBuilder setHeaderIcon(Drawable icon) {
- throw new RuntimeException("Method not supported.");
+ public SubMenu setHeaderIcon(Drawable icon) {
+ return (SubMenu) super.setHeaderIconInt(icon);
}
- public SubMenuBuilder setHeaderIcon(int iconRes) {
- throw new RuntimeException("Method not supported.");
+ public SubMenu setHeaderIcon(int iconRes) {
+ return (SubMenu) super.setHeaderIconInt(iconRes);
}
- public SubMenuBuilder setHeaderTitle(CharSequence title) {
- throw new RuntimeException("Method not supported.");
+ public SubMenu setHeaderTitle(CharSequence title) {
+ return (SubMenu) super.setHeaderTitleInt(title);
}
- public SubMenuBuilder setHeaderTitle(int titleRes) {
- throw new RuntimeException("Method not supported.");
+ public SubMenu setHeaderTitle(int titleRes) {
+ return (SubMenu) super.setHeaderTitleInt(titleRes);
+ }
+
+ public SubMenu setHeaderView(View view) {
+ return (SubMenu) super.setHeaderViewInt(view);
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuItemImpl item) {
+ return mParentMenu.expandItemActionView(item);
}
@Override
- public SubMenuBuilder setHeaderView(View view) {
- throw new RuntimeException("Method not supported.");
+ public boolean collapseItemActionView(MenuItemImpl item) {
+ return mParentMenu.collapseItemActionView(item);
}
@Override
- public void clearHeader() {
- throw new RuntimeException("Method not supported.");
+ public String getActionViewStatesKey() {
+ final int itemId = mItem != null ? mItem.getItemId() : 0;
+ if (itemId == 0) {
+ return null;
+ }
+ return super.getActionViewStatesKey() + ":" + itemId;
}
-}
\ No newline at end of file
+}
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/ActionBarView.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/ActionBarView.java
index 2eb932ebe..86296c874 100644
--- a/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/ActionBarView.java
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/ActionBarView.java
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBar;
+import android.support.v4.view.Menu;
import android.support.v4.view.Window;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -23,7 +24,10 @@ import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.view.menu.ActionMenuItem;
-import com.actionbarsherlock.internal.view.menu.ActionMenuItemView;
+import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter;
+import com.actionbarsherlock.internal.view.menu.ActionMenuView;
+import com.actionbarsherlock.internal.view.menu.MenuBuilder;
+import com.actionbarsherlock.internal.view.menu.MenuPresenter;
public final class ActionBarView extends RelativeLayout {
/** Default display options if none are defined in the theme. */
@@ -34,6 +38,8 @@ public final class ActionBarView extends RelativeLayout {
+ private final Context mContext;
+
private final View mHomeAsUpView;
private final ViewGroup mHomeLayout;
private final ActionMenuItem mLogoNavItem;
@@ -46,6 +52,7 @@ public final class ActionBarView extends RelativeLayout {
/** Indeterminate progress bar. */
private final ProgressBar mIndeterminateProgress;
+ private boolean mAllowsIndeterminateProgress = false;
/** List view. */
private final Spinner mSpinner;
@@ -60,13 +67,16 @@ public final class ActionBarView extends RelativeLayout {
private ImageView mIconView;
private Drawable mLogo;
private Drawable mIcon;
- private final Drawable mDivider;
- /** Container for all action items. */
- private final LinearLayout mActionsView;
+ /** Container for action item view. */
+ private final FrameLayout mActionsView;
+ private MenuBuilder mOptionsMenu;
+ private ActionMenuView mMenuView;
+ private ActionMenuPresenter mActionMenuPresenter;
/** Container for all tab items. */
private final LinearLayout mTabsView;
+ private final ViewGroup mTabViewContainer;
/**
* Display state flags.
@@ -102,6 +112,7 @@ public final class ActionBarView extends RelativeLayout {
public ActionBarView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mContext = context;
mIsConstructing = true;
LayoutInflater.from(context).inflate(R.layout.abs__action_bar, this, true);
@@ -192,6 +203,7 @@ public final class ActionBarView extends RelativeLayout {
mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
mTabsView = (LinearLayout)findViewById(R.id.abs__nav_tabs);
+ mTabViewContainer = (ViewGroup)findViewById(R.id.abs__nav_tabs_layout);
//// CUSTOM VIEW ////
@@ -210,8 +222,7 @@ public final class ActionBarView extends RelativeLayout {
- mActionsView = (LinearLayout)findViewById(R.id.abs__actions);
- mDivider = a.getDrawable(R.styleable.SherlockTheme_abDivider);
+ mActionsView = (FrameLayout)findViewById(R.id.abs__actions);
mIndeterminateProgress = (ProgressBar)findViewById(R.id.abs__iprogress);
@@ -293,6 +304,9 @@ public final class ActionBarView extends RelativeLayout {
// Show tabs if in tabs navigation mode.
mTabsView.setVisibility(isTab ? View.VISIBLE : View.GONE);
+ if (mTabViewContainer != null) {
+ mTabViewContainer.setVisibility(isTab ? View.VISIBLE : View.GONE);
+ }
//Show title view if we are not in list navigation, not showing custom
//view, and the show title flag is true
@@ -305,6 +319,53 @@ public final class ActionBarView extends RelativeLayout {
mCustomView.setVisibility(isStandard && displayCustom ? View.VISIBLE : View.GONE);
}
+ public void initIndeterminateProgress() {
+ mAllowsIndeterminateProgress = true;
+ }
+
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+ if (menu == mOptionsMenu) return;
+
+ if (mOptionsMenu != null) {
+ mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+ }
+
+ MenuBuilder builder = (MenuBuilder) menu;
+ mOptionsMenu = builder;
+ if (mMenuView != null) {
+ final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
+ if (oldParent != null) {
+ oldParent.removeView(mMenuView);
+ }
+ }
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter(mContext);
+ mActionMenuPresenter.setCallback(cb);
+ mActionMenuPresenter.setId(R.id.abs__action_menu_presenter);
+ }
+
+ ActionMenuView menuView;
+ final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.FILL_PARENT);
+ configPresenters(builder);
+ menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
+ final ViewGroup oldParent = (ViewGroup) menuView.getParent();
+ if (oldParent != null && oldParent != this) {
+ oldParent.removeView(menuView);
+ }
+ mActionsView.addView(menuView, layoutParams);
+ mMenuView = menuView;
+ }
+
+ private void configPresenters(MenuBuilder builder) {
+ if (builder != null) {
+ builder.addMenuPresenter(mActionMenuPresenter);
+ } else {
+ mActionMenuPresenter.initForMenu(mContext, null);
+ mActionMenuPresenter.updateMenuView(true);
+ }
+ }
+
// ------------------------------------------------------------------------
// ACTION BAR API
// ------------------------------------------------------------------------
@@ -451,7 +512,9 @@ public final class ActionBarView extends RelativeLayout {
}
public void setProgressBarIndeterminateVisibility(boolean visible) {
- mIndeterminateProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
+ if (mAllowsIndeterminateProgress) {
+ mIndeterminateProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
}
public void setNavigationMode(int mode) {
@@ -495,37 +558,6 @@ public final class ActionBarView extends RelativeLayout {
mTitleLayout.setText(resId);
}
- // ------------------------------------------------------------------------
- // ACTION ITEMS SUPPORT
- // ------------------------------------------------------------------------
-
- public ActionMenuItemView newItem() {
- ActionMenuItemView item = (ActionMenuItemView)LayoutInflater.from(getContext()).inflate(R.layout.abs__action_bar_item_layout, mActionsView, false);
- return item;
- }
-
- public void addItem(ActionMenuItemView item) {
- if (mDivider != null) {
- ImageView divider = new ImageView(getContext());
- divider.setImageDrawable(mDivider);
- divider.setScaleType(ImageView.ScaleType.FIT_XY);
-
- LinearLayout.LayoutParams dividerParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.FILL_PARENT
- );
-
- mActionsView.addView(divider, dividerParams);
- item.setDivider(divider);
- }
-
- mActionsView.addView(item);
- }
-
- public void removeAllItems() {
- mActionsView.removeAllViews();
- }
-
// ------------------------------------------------------------------------
// HELPER INTERFACES AND HELPER CLASSES
// ------------------------------------------------------------------------
diff --git a/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java b/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java
new file mode 100644
index 000000000..7d78ce3bb
--- /dev/null
+++ b/actionbarsherlock/library/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java
@@ -0,0 +1,1237 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+package com.actionbarsherlock.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+/**
+ * This is a super-stripped version of LinearLayout from API 14 which supports
+ * only horizontal layouts.
+ */
+public class IcsLinearLayout extends ViewGroup {
+
+ private static final int[] LinearLayout = new int[] {
+ /* 0 */ android.R.attr.divider,
+ /* 1 */ android.R.attr.dividerPadding,
+ /* 2 */ android.R.attr.weightSum,
+ /* 3 */ android.R.attr.measureWithLargestChild,
+ /* 4 */ android.R.attr.showDividers,
+ /* 5 */ android.R.attr.baselineAlignedChildIndex,
+ };
+ private static final int LinearLayout_divider = 0;
+ private static final int LinearLayout_dividerPadding = 1;
+ private static final int LinearLayout_weightSum = 2;
+ private static final int LinearLayout_measureWithLargestChild = 3;
+ private static final int LinearLayout_showDividers = 4;
+ private static final int LinearLayout_baselineAlignedChildIndex = 5;
+
+
+ /**
+ * Don't show any dividers.
+ */
+ public static final int SHOW_DIVIDER_NONE = 0;
+ /**
+ * Show a divider at the beginning of the group.
+ */
+ public static final int SHOW_DIVIDER_BEGINNING = 1;
+ /**
+ * Show dividers between each item in the group.
+ */
+ public static final int SHOW_DIVIDER_MIDDLE = 2;
+ /**
+ * Show a divider at the end of the group.
+ */
+ public static final int SHOW_DIVIDER_END = 4;
+
+ /**
+ * Whether the children of this layout are baseline aligned. Only applicable
+ * if {@link #mOrientation} is horizontal.
+ */
+ private boolean mBaselineAligned = true;
+
+ /**
+ * If this layout is part of another layout that is baseline aligned,
+ * use the child at this index as the baseline.
+ *
+ * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
+ * with whether the children of this layout are baseline aligned.
+ */
+ private int mBaselineAlignedChildIndex = -1;
+
+ /**
+ * The additional offset to the child's baseline.
+ * We'll calculate the baseline of this layout as we measure vertically; for
+ * horizontal linear layouts, the offset of 0 is appropriate.
+ */
+ private int mBaselineChildTop = 0;
+
+ private int mGravity = Gravity.TOP;
+
+ private int mTotalLength;
+
+ private float mWeightSum;
+
+ private boolean mUseLargestChild;
+
+ private int[] mMaxAscent;
+ private int[] mMaxDescent;
+
+ private static final int VERTICAL_GRAVITY_COUNT = 4;
+
+ private static final int INDEX_CENTER_VERTICAL = 0;
+ private static final int INDEX_TOP = 1;
+ private static final int INDEX_BOTTOM = 2;
+ private static final int INDEX_FILL = 3;
+
+ private Drawable mDivider;
+ private int mDividerWidth;
+ private int mShowDividers;
+ private int mDividerPadding;
+
+ public IcsLinearLayout(Context context) {
+ super(context);
+ }
+
+ public IcsLinearLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public IcsLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/LinearLayout, defStyle, 0);
+
+ //int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
+ //if (index >= 0) {
+ // setOrientation(index);
+ //}
+
+ //index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
+ //if (index >= 0) {
+ // setGravity(index);
+ //}
+
+ //boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
+ //if (!baselineAligned) {
+ // setBaselineAligned(baselineAligned);
+ //}
+
+ mWeightSum = a.getFloat(/*com.android.internal.R.styleable.*/LinearLayout_weightSum, -1.0f);
+
+ mBaselineAlignedChildIndex = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_baselineAlignedChildIndex, -1);
+
+ mUseLargestChild = a.getBoolean(/*com.android.internal.R.styleable.*/LinearLayout_measureWithLargestChild, false);
+
+ setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider));
+ mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE);
+ mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0);
+
+ a.recycle();
+ }
+
+ /**
+ * Set how dividers should be shown between items in this layout
+ *
+ * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
+ * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
+ * or {@link #SHOW_DIVIDER_NONE} to show no dividers.
+ */
+ public void setShowDividers(int showDividers) {
+ if (showDividers != mShowDividers) {
+ requestLayout();
+ }
+ mShowDividers = showDividers;
+ }
+
+ //@Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ /**
+ * @return A flag set indicating how dividers should be shown around items.
+ * @see #setShowDividers(int)
+ */
+ public int getShowDividers() {
+ return mShowDividers;
+ }
+
+ /**
+ * Set a drawable to be used as a divider between items.
+ * @param divider Drawable that will divide each item.
+ * @see #setShowDividers(int)
+ */
+ public void setDividerDrawable(Drawable divider) {
+ if (divider == mDivider) {
+ return;
+ }
+ mDivider = divider;
+ if (divider != null) {
+ mDividerWidth = divider.getIntrinsicWidth();
+ } else {
+ mDividerWidth = 0;
+ }
+ setWillNotDraw(divider == null);
+ requestLayout();
+ }
+
+ /**
+ * Set padding displayed on both ends of dividers.
+ *
+ * @param padding Padding value in pixels that will be applied to each end
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #getDividerPadding()
+ */
+ public void setDividerPadding(int padding) {
+ mDividerPadding = padding;
+ }
+
+ /**
+ * Get the padding size used to inset dividers in pixels
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #setDividerPadding(int)
+ */
+ public int getDividerPadding() {
+ return mDividerPadding;
+ }
+
+ /**
+ * Get the width of the current divider drawable.
+ *
+ * @hide Used internally by framework.
+ */
+ public int getDividerWidth() {
+ return mDividerWidth;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDivider == null) {
+ return;
+ }
+
+ final int count = getVirtualChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child != null && child.getVisibility() != GONE) {
+ if (hasDividerBeforeChildAt(i)) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int left = child.getLeft() - lp.leftMargin;
+ drawVerticalDivider(canvas, left);
+ }
+ }
+ }
+
+ if (hasDividerBeforeChildAt(count)) {
+ final View child = getVirtualChildAt(count - 1);
+ int right = 0;
+ if (child == null) {
+ right = getWidth() - getPaddingRight() - mDividerWidth;
+ } else {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ right = child.getRight() + lp.rightMargin;
+ }
+ drawVerticalDivider(canvas, right);
+ }
+ }
+
+ void drawVerticalDivider(Canvas canvas, int left) {
+ mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
+ left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
+ mDivider.draw(canvas);
+ }
+
+ /**
+ * Indicates whether widgets contained within this layout are aligned
+ * on their baseline or not.
+ *
+ * @return true when widgets are baseline-aligned, false otherwise
+ */
+ public boolean isBaselineAligned() {
+ return mBaselineAligned;
+ }
+
+ /**
+ * Defines whether widgets contained in this layout are
+ * baseline-aligned or not.
+ *
+ * @param baselineAligned true to align widgets on their baseline,
+ * false otherwise
+ *
+ * @attr ref android.R.styleable#LinearLayout_baselineAligned
+ */
+ public void setBaselineAligned(boolean baselineAligned) {
+ mBaselineAligned = baselineAligned;
+ }
+
+ /**
+ * When true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * @return True to measure children with a weight using the minimum
+ * size of the largest child, false otherwise.
+ */
+ public boolean isMeasureWithLargestChildEnabled() {
+ return mUseLargestChild;
+ }
+
+ /**
+ * When set to true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * Disabled by default.
+ *
+ * @param enabled True to measure children with a weight using the
+ * minimum size of the largest child, false otherwise.
+ */
+ public void setMeasureWithLargestChildEnabled(boolean enabled) {
+ mUseLargestChild = enabled;
+ }
+
+ @Override
+ public int getBaseline() {
+ if (mBaselineAlignedChildIndex < 0) {
+ return super.getBaseline();
+ }
+
+ if (getChildCount() <= mBaselineAlignedChildIndex) {
+ throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+ + "set to an index that is out of bounds.");
+ }
+
+ final View child = getChildAt(mBaselineAlignedChildIndex);
+ final int childBaseline = child.getBaseline();
+
+ if (childBaseline == -1) {
+ if (mBaselineAlignedChildIndex == 0) {
+ // this is just the default case, safe to return -1
+ return -1;
+ }
+ // the user picked an index that points to something that doesn't
+ // know how to calculate its baseline.
+ throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+ + "points to a View that doesn't know how to get its baseline.");
+ }
+
+ // TODO: This should try to take into account the virtual offsets
+ // (See getNextLocationOffset and getLocationOffset)
+ // We should add to childTop:
+ // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex])
+ // and also add:
+ // getLocationOffset(child)
+ int childTop = mBaselineChildTop;
+
+ IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();
+ return childTop + lp.topMargin + childBaseline;
+ }
+
+ /**
+ * @return The index of the child that will be used if this layout is
+ * part of a larger layout that is baseline aligned, or -1 if none has
+ * been set.
+ */
+ public int getBaselineAlignedChildIndex() {
+ return mBaselineAlignedChildIndex;
+ }
+
+ /**
+ * @param i The index of the child that will be used if this layout is
+ * part of a larger layout that is baseline aligned.
+ *
+ * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
+ */
+ public void setBaselineAlignedChildIndex(int i) {
+ if ((i < 0) || (i >= getChildCount())) {
+ throw new IllegalArgumentException("base aligned child index out "
+ + "of range (0, " + getChildCount() + ")");
+ }
+ mBaselineAlignedChildIndex = i;
+ }
+
+ /**
+ * Returns the view at the specified index. This method can be overriden
+ * to take into account virtual children. Refer to
+ * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+ * for an example.
+ *
+ * @param index the child's index
+ * @return the child at the specified index
+ */
+ View getVirtualChildAt(int index) {
+ return getChildAt(index);
+ }
+
+ /**
+ * Returns the virtual number of children. This number might be different
+ * than the actual number of children if the layout can hold virtual
+ * children. Refer to
+ * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+ * for an example.
+ *
+ * @return the virtual number of children
+ */
+ int getVirtualChildCount() {
+ return getChildCount();
+ }
+
+ /**
+ * Returns the desired weights sum.
+ *
+ * @return A number greater than 0.0f if the weight sum is defined, or
+ * a number lower than or equals to 0.0f if not weight sum is
+ * to be used.
+ */
+ public float getWeightSum() {
+ return mWeightSum;
+ }
+
+ /**
+ * Defines the desired weights sum. If unspecified the weights sum is computed
+ * at layout time by adding the layout_weight of each child.
+ *
+ * This can be used for instance to give a single child 50% of the total
+ * available space by giving it a layout_weight of 0.5 and setting the
+ * weightSum to 1.0.
+ *
+ * @param weightSum a number greater than 0.0f, or a number lower than or equals
+ * to 0.0f if the weight sum should be computed from the children's
+ * layout_weight
+ */
+ public void setWeightSum(float weightSum) {
+ mWeightSum = Math.max(0.0f, weightSum);
+ }
+
+ /**
+ * Determines where to position dividers between children.
+ *
+ * @param childIndex Index of child to check for preceding divider
+ * @return true if there should be a divider before the child at childIndex
+ * @hide Pending API consideration. Currently only used internally by the system.
+ */
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+ } else if (childIndex == getChildCount()) {
+ return (mShowDividers & SHOW_DIVIDER_END) != 0;
+ } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
+ boolean hasVisibleViewBefore = false;
+ for (int i = childIndex - 1; i >= 0; i--) {
+ if (getChildAt(i).getVisibility() != GONE) {
+ hasVisibleViewBefore = true;
+ break;
+ }
+ }
+ return hasVisibleViewBefore;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mTotalLength = 0;
+ int maxHeight = 0;
+ int childState = 0;
+ int alternativeMaxHeight = 0;
+ int weightedMaxHeight = 0;
+ boolean allFillParent = true;
+ float totalWeight = 0;
+
+ final int count = getVirtualChildCount();
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean matchHeight = false;
+
+ if (mMaxAscent == null || mMaxDescent == null) {
+ mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
+ mMaxDescent = new int[VERTICAL_GRAVITY_COUNT];
+ }
+
+ final int[] maxAscent = mMaxAscent;
+ final int[] maxDescent = mMaxDescent;
+
+ maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+ maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+
+ final boolean baselineAligned = mBaselineAligned;
+ final boolean useLargestChild = mUseLargestChild;
+
+ final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
+
+ int largestChildWidth = Integer.MIN_VALUE;
+
+ // See how wide everyone is. Also remember max height.
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ if (hasDividerBeforeChildAt(i)) {
+ mTotalLength += mDividerWidth;
+ }
+
+ final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams)
+ child.getLayoutParams();
+
+ totalWeight += lp.weight;
+
+ if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) {
+ // Optimization: don't bother measuring children who are going to use
+ // leftover space. These views will get measured again down below if
+ // there is any leftover space.
+ if (isExactly) {
+ mTotalLength += lp.leftMargin + lp.rightMargin;
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength +
+ lp.leftMargin + lp.rightMargin);
+ }
+
+ // Baseline alignment requires to measure widgets to obtain the
+ // baseline offset (in particular for TextViews). The following
+ // defeats the optimization mentioned above. Allow the child to
+ // use as much space as it wants because we can shrink things
+ // later (and re-measure).
+ if (baselineAligned) {
+ final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ child.measure(freeSpec, freeSpec);
+ }
+ } else {
+ int oldWidth = Integer.MIN_VALUE;
+
+ if (lp.width == 0 && lp.weight > 0) {
+ // widthMode is either UNSPECIFIED or AT_MOST, and this
+ // child
+ // wanted to stretch to fill available space. Translate that to
+ // WRAP_CONTENT so that it does not end up with a width of 0
+ oldWidth = 0;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ }
+
+ // Determine how big this child would like to be. If this or
+ // previous children have given a weight, then we allow it to
+ // use all available space (and we will shrink things later
+ // if needed).
+ measureChildBeforeLayout(child, i, widthMeasureSpec,
+ totalWeight == 0 ? mTotalLength : 0,
+ heightMeasureSpec, 0);
+
+ if (oldWidth != Integer.MIN_VALUE) {
+ lp.width = oldWidth;
+ }
+
+ final int childWidth = child.getMeasuredWidth();
+ if (isExactly) {
+ mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+ lp.rightMargin + getNextLocationOffset(child));
+ }
+
+ if (useLargestChild) {
+ largestChildWidth = Math.max(childWidth, largestChildWidth);
+ }
+ }
+
+ boolean matchHeightLocally = false;
+ if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
+ // The height of the linear layout will scale, and at least one
+ // child said it wanted to match our height. Set a flag indicating that
+ // we need to remeasure at least that view when we know our height.
+ matchHeight = true;
+ matchHeightLocally = true;
+ }
+
+ final int margin = lp.topMargin + lp.bottomMargin;
+ final int childHeight = child.getMeasuredHeight() + margin;
+ childState = combineMeasuredStatesInt(childState, getMeasuredStateInt(child));
+
+ if (baselineAligned) {
+ final int childBaseline = child.getBaseline();
+ if (childBaseline != -1) {
+ // Translates the child's vertical gravity into an index
+ // in the range 0..VERTICAL_GRAVITY_COUNT
+ final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+ & Gravity.VERTICAL_GRAVITY_MASK;
+ final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+ & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+ maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+ maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
+ }
+ }
+
+ maxHeight = Math.max(maxHeight, childHeight);
+
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
+ if (lp.weight > 0) {
+ /*
+ * Heights of weighted Views are bogus if we end up
+ * remeasuring, so keep them separate.
+ */
+ weightedMaxHeight = Math.max(weightedMaxHeight,
+ matchHeightLocally ? margin : childHeight);
+ } else {
+ alternativeMaxHeight = Math.max(alternativeMaxHeight,
+ matchHeightLocally ? margin : childHeight);
+ }
+
+ i += getChildrenSkipCount(child, i);
+ }
+
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
+ mTotalLength += mDividerWidth;
+ }
+
+ // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+ // the most common case
+ if (maxAscent[INDEX_TOP] != -1 ||
+ maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+ maxAscent[INDEX_BOTTOM] != -1 ||
+ maxAscent[INDEX_FILL] != -1) {
+ final int ascent = Math.max(maxAscent[INDEX_FILL],
+ Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+ Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+ final int descent = Math.max(maxDescent[INDEX_FILL],
+ Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+ Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+ maxHeight = Math.max(maxHeight, ascent + descent);
+ }
+
+ if (useLargestChild &&
+ (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams)
+ child.getLayoutParams();
+ if (isExactly) {
+ mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ }
+ }
+ }
+
+ // Add in our padding
+ mTotalLength += getPaddingLeft() + getPaddingRight();
+
+ int widthSize = mTotalLength;
+
+ // Check against our minimum width
+ widthSize = Math.max(widthSize, getSuggestedMinimumWidth());
+
+ // Reconcile our calculated size with the widthMeasureSpec
+ int widthSizeAndState = resolveSizeAndStateInt(widthSize, widthMeasureSpec, 0);
+ widthSize = widthSizeAndState & MEASURED_SIZE_MASK;
+
+ // Either expand children with weight to take up available space or
+ // shrink them if they extend beyond our current bounds
+ int delta = widthSize - mTotalLength;
+ if (delta != 0 && totalWeight > 0.0f) {
+ float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+ maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+ maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+ maxHeight = -1;
+
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final IcsLinearLayout.LayoutParams lp =
+ (IcsLinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ // Child said it could absorb extra space -- give him his share
+ int share = (int) (childExtra * delta / weightSum);
+ weightSum -= childExtra;
+ delta -= share;
+
+ final int childHeightMeasureSpec = getChildMeasureSpec(
+ heightMeasureSpec,
+ getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin,
+ lp.height);
+
+ // TODO: Use a field like lp.isMeasured to figure out if this
+ // child has been previously measured
+ if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
+ // child was measured once already above ... base new measurement
+ // on stored values
+ int childWidth = child.getMeasuredWidth() + share;
+ if (childWidth < 0) {
+ childWidth = 0;
+ }
+
+ child.measure(
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+ childHeightMeasureSpec);
+ } else {
+ // child was skipped in the loop above. Measure for this first time here
+ child.measure(MeasureSpec.makeMeasureSpec(
+ share > 0 ? share : 0, MeasureSpec.EXACTLY),
+ childHeightMeasureSpec);
+ }
+
+ // Child may now not fit in horizontal dimension.
+ childState = combineMeasuredStatesInt(childState,
+ getMeasuredStateInt(child) & MEASURED_STATE_MASK);
+ }
+
+ if (isExactly) {
+ mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ }
+
+ boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
+ lp.height == LayoutParams.MATCH_PARENT;
+
+ final int margin = lp.topMargin + lp .bottomMargin;
+ int childHeight = child.getMeasuredHeight() + margin;
+ maxHeight = Math.max(maxHeight, childHeight);
+ alternativeMaxHeight = Math.max(alternativeMaxHeight,
+ matchHeightLocally ? margin : childHeight);
+
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
+
+ if (baselineAligned) {
+ final int childBaseline = child.getBaseline();
+ if (childBaseline != -1) {
+ // Translates the child's vertical gravity into an index in the range 0..2
+ final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+ & Gravity.VERTICAL_GRAVITY_MASK;
+ final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+ & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+ maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+ maxDescent[index] = Math.max(maxDescent[index],
+ childHeight - childBaseline);
+ }
+ }
+ }
+
+ // Add in our padding
+ mTotalLength += getPaddingLeft() + getPaddingRight();
+ // TODO: Should we update widthSize with the new total length?
+
+ // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+ // the most common case
+ if (maxAscent[INDEX_TOP] != -1 ||
+ maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+ maxAscent[INDEX_BOTTOM] != -1 ||
+ maxAscent[INDEX_FILL] != -1) {
+ final int ascent = Math.max(maxAscent[INDEX_FILL],
+ Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+ Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+ final int descent = Math.max(maxDescent[INDEX_FILL],
+ Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+ Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+ maxHeight = Math.max(maxHeight, ascent + descent);
+ }
+ } else {
+ alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
+
+ // We have no limit, so make all weighted views as wide as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final IcsLinearLayout.LayoutParams lp =
+ (IcsLinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
+ }
+
+ if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
+ maxHeight = alternativeMaxHeight;
+ }
+
+ maxHeight += getPaddingTop() + getPaddingBottom();
+
+ // Check against our minimum height
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+
+ setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
+ resolveSizeAndStateInt(maxHeight, heightMeasureSpec,
+ (childState<>{@link #MEASURED_STATE_MASK}.
+ */
+ public static int getMeasuredStateInt(View child) {
+ return (child.getMeasuredWidth()&MEASURED_STATE_MASK)
+ | ((child.getMeasuredHeight()>>MEASURED_HEIGHT_STATE_SHIFT)
+ & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ private void forceUniformHeight(int count, int widthMeasureSpec) {
+ // Pretend that the linear layout has an exact size. This is the measured height of
+ // ourselves. The measured height should be the max height of the children, changed
+ // to accomodate the heightMesureSpec from the parent
+ int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+ MeasureSpec.EXACTLY);
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+ if (child.getVisibility() != GONE) {
+ IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();
+
+ if (lp.height == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured width
+ // FIXME: this may not be right for something like wrapping text?
+ int oldWidth = lp.width;
+ lp.width = child.getMeasuredWidth();
+
+ // Remeasure with new dimensions
+ measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+ lp.width = oldWidth;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the number of children to skip after measuring/laying out
+ * the specified child.
+ *
+ * @param child the child after which we want to skip children
+ * @param index the index of the child after which we want to skip children
+ * @return the number of children to skip, 0 by default
+ */
+ int getChildrenSkipCount(View child, int index) {
+ return 0;
+ }
+
+ /**
+ * Returns the size (width or height) that should be occupied by a null
+ * child.
+ *
+ * @param childIndex the index of the null child
+ * @return the width or height of the child depending on the orientation
+ */
+ int measureNullChild(int childIndex) {
+ return 0;
+ }
+
+ /**
+ * Measure the child according to the parent's measure specs. This
+ * method should be overriden by subclasses to force the sizing of
+ * children. This method is called by {@link #measureVertical(int, int)} and
+ * {@link #measureHorizontal(int, int)}.
+ *
+ * @param child the child to measure
+ * @param childIndex the index of the child in this view
+ * @param widthMeasureSpec horizontal space requirements as imposed by the parent
+ * @param totalWidth extra space that has been used up by the parent horizontally
+ * @param heightMeasureSpec vertical space requirements as imposed by the parent
+ * @param totalHeight extra space that has been used up by the parent vertically
+ */
+ void measureChildBeforeLayout(View child, int childIndex,
+ int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
+ int totalHeight) {
+ measureChildWithMargins(child, widthMeasureSpec, totalWidth,
+ heightMeasureSpec, totalHeight);
+ }
+
+ /**
+ * Return the location offset of the specified child. This can be used
+ * by subclasses to change the location of a given widget.
+ *
+ * @param child the child for which to obtain the location offset
+ * @return the location offset in pixels
+ */
+ int getLocationOffset(View child) {
+ return 0;
+ }
+
+ /**
+ * Return the size offset of the next sibling of the specified child.
+ * This can be used by subclasses to change the location of the widget
+ * following child
.
+ *
+ * @param child the child whose next sibling will be moved
+ * @return the location offset of the next child in pixels
+ */
+ int getNextLocationOffset(View child) {
+ return 0;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final boolean isLayoutRtl = false;
+ final int paddingTop = getPaddingTop();
+
+ int childTop;
+ int childLeft;
+
+ // Where bottom of child should go
+ final int height = getBottom() - getTop();
+ int childBottom = height - getPaddingBottom();
+
+ // Space available for child
+ int childSpace = height - paddingTop - getPaddingBottom();
+
+ final int count = getVirtualChildCount();
+
+ //final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+ final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ final boolean baselineAligned = mBaselineAligned;
+
+ final int[] maxAscent = mMaxAscent;
+ final int[] maxDescent = mMaxDescent;
+
+ childLeft = getPaddingLeft();
+
+ int start = 0;
+ int dir = 1;
+ //In case of RTL, start drawing from the last child.
+ if (isLayoutRtl) {
+ start = count - 1;
+ dir = -1;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int childIndex = start + dir * i;
+ final View child = getVirtualChildAt(childIndex);
+
+ if (child == null) {
+ childLeft += measureNullChild(childIndex);
+ } else if (child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+ int childBaseline = -1;
+
+ final IcsLinearLayout.LayoutParams lp =
+ (IcsLinearLayout.LayoutParams) child.getLayoutParams();
+
+ if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
+ childBaseline = child.getBaseline();
+ }
+
+ int gravity = lp.gravity;
+ if (gravity < 0) {
+ gravity = minorGravity;
+ }
+
+ switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ childTop = paddingTop + lp.topMargin;
+ if (childBaseline != -1) {
+ childTop += maxAscent[INDEX_TOP] - childBaseline;
+ }
+ break;
+
+ case Gravity.CENTER_VERTICAL:
+ // Removed support for baseline alignment when layout_gravity or
+ // gravity == center_vertical. See bug #1038483.
+ // Keep the code around if we need to re-enable this feature
+ // if (childBaseline != -1) {
+ // // Align baselines vertically only if the child is smaller than us
+ // if (childSpace - childHeight > 0) {
+ // childTop = paddingTop + (childSpace / 2) - childBaseline;
+ // } else {
+ // childTop = paddingTop + (childSpace - childHeight) / 2;
+ // }
+ // } else {
+ childTop = paddingTop + ((childSpace - childHeight) / 2)
+ + lp.topMargin - lp.bottomMargin;
+ break;
+
+ case Gravity.BOTTOM:
+ childTop = childBottom - childHeight - lp.bottomMargin;
+ if (childBaseline != -1) {
+ int descent = child.getMeasuredHeight() - childBaseline;
+ childTop -= (maxDescent[INDEX_BOTTOM] - descent);
+ }
+ break;
+ default:
+ childTop = paddingTop;
+ break;
+ }
+
+ if (hasDividerBeforeChildAt(childIndex)) {
+ childLeft += mDividerWidth;
+ }
+
+ childLeft += lp.leftMargin;
+ setChildFrame(child, childLeft + getLocationOffset(child), childTop,
+ childWidth, childHeight);
+ childLeft += childWidth + lp.rightMargin +
+ getNextLocationOffset(child);
+
+ i += getChildrenSkipCount(child, childIndex);
+ }
+ }
+ }
+
+ private void setChildFrame(View child, int left, int top, int width, int height) {
+ child.layout(left, top, left + width, top + height);
+ }
+
+ /**
+ * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If
+ * this layout has a VERTICAL orientation, this controls where all the child
+ * views are placed if there is extra vertical space. If this layout has a
+ * HORIZONTAL orientation, this controls the alignment of the children.
+ *
+ * @param gravity See {@link android.view.Gravity}
+ *
+ * @attr ref android.R.styleable#LinearLayout_gravity
+ */
+ public void setGravity(int gravity) {
+ if (mGravity != gravity) {
+ if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.TOP;
+ }
+
+ mGravity = gravity;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new IcsLinearLayout.LayoutParams(getContext(), attrs);
+ }
+
+ /**
+ * Returns a set of layout parameters with a width of
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * when the layout's orientation is {@link #VERTICAL}. When the orientation is
+ * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
+ * and the height to {@link LayoutParams#WRAP_CONTENT}.
+ */
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+
+ // Override to allow type-checking of LayoutParams.
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof IcsLinearLayout.LayoutParams;
+ }
+
+ /**
+ * Per-child layout information associated with ViewLinearLayout.
+ *
+ * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
+ * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
+ */
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Indicates how much of the extra space in the LinearLayout will be
+ * allocated to the view associated with these LayoutParams. Specify
+ * 0 if the view should not be stretched. Otherwise the extra pixels
+ * will be pro-rated among all views whose weight is greater than 0.
+ */
+ public float weight;
+
+ /**
+ * Gravity for the view associated with these LayoutParams.
+ *
+ * @see android.view.Gravity
+ */
+ public int gravity = -1;
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ //TypedArray a =
+ // c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
+
+ weight = 0;//a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
+ gravity = -1;//a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
+
+ //a.recycle();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ weight = 0;
+ }
+
+ /**
+ * Creates a new set of layout parameters with the specified width, height
+ * and weight.
+ *
+ * @param width the width, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param height the height, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param weight the weight
+ */
+ public LayoutParams(int width, int height, float weight) {
+ super(width, height);
+ this.weight = weight;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(ViewGroup.LayoutParams p) {
+ super(p);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+ }
+}
diff --git a/actionbarsherlock/plugins/maps/AndroidManifest.xml b/actionbarsherlock/plugins/maps/AndroidManifest.xml
index 8615e26fa..7b80ceee3 100644
--- a/actionbarsherlock/plugins/maps/AndroidManifest.xml
+++ b/actionbarsherlock/plugins/maps/AndroidManifest.xml
@@ -3,8 +3,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-plugins
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/plugins/maps/src/android/support/v4/app/FragmentMapActivity.java b/actionbarsherlock/plugins/maps/src/android/support/v4/app/FragmentMapActivity.java
index 44604ad1d..6bfe29c66 100644
--- a/actionbarsherlock/plugins/maps/src/android/support/v4/app/FragmentMapActivity.java
+++ b/actionbarsherlock/plugins/maps/src/android/support/v4/app/FragmentMapActivity.java
@@ -21,41 +21,49 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.internal.app.ActionBarWrapper;
-import com.actionbarsherlock.internal.app.ActionBarImpl;
-import com.actionbarsherlock.internal.view.menu.MenuBuilder;
-import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
-import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
-import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
-import com.actionbarsherlock.internal.view.menu.MenuWrapper;
-import com.google.android.maps.MapActivity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import com.google.android.maps.MapActivity;
import android.os.Message;
import android.os.Parcelable;
import android.support.v4.view.ActionMode;
import android.support.v4.view.Menu;
-import android.support.v4.view.MenuInflater;
import android.support.v4.view.MenuItem;
import android.support.v4.view.Window;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.app.ActionBarImpl;
+import com.actionbarsherlock.internal.app.ActionBarWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuBuilder;
+import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
+import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
+import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuPresenter;
+import com.actionbarsherlock.internal.view.menu.MenuWrapper;
+import com.actionbarsherlock.internal.widget.ActionBarView;
/**
- * Base class for activities that want to use the support-based ActionBar,
- * Fragment, Loader, and Google Map APIs.
+ * Base class for activities that want to use the support-based
+ * {@link android.support.v4.app.Fragment},
+ * {@link android.support.v4.content.Loader}, and
+ * {@link android.support.v4.app.ActionBar} APIs.
+ *
+ * When using this class as opposed to new platform's built-in fragment
+ * and loader support, you must use the {@link #getSupportFragmentManager()}
+ * and {@link #getSupportLoaderManager()} methods respectively to access
+ * those features.
*
*
Known limitations:
*
@@ -70,7 +78,7 @@ import android.widget.FrameLayout;
* prior to Honeycomb, where the state is saved before pausing. To address this,
* when running on platforms prior to Honeycomb an exception will not be thrown
* if you change fragments between the state save and the activity being stopped.
- * This means that is some cases if the activity is restored from its last saved
+ * This means that in some cases if the activity is restored from its last saved
* state, this may be a snapshot slightly before what the user last saw.
*
*/
@@ -85,12 +93,6 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
static final int MSG_REALLY_STOPPED = 1;
static final int MSG_RESUME_PENDING = 2;
- private static final int WINDOW_FLAG_ACTION_BAR = 1 << Window.FEATURE_ACTION_BAR;
- private static final int WINDOW_FLAG_ACTION_BAR_ITEM_TEXT = 1 << Window.FEATURE_ACTION_BAR_ITEM_TEXT;
- private static final int WINDOW_FLAG_ACTION_BAR_OVERLAY = 1 << Window.FEATURE_ACTION_BAR_OVERLAY;
- private static final int WINDOW_FLAG_ACTION_MODE_OVERLAY = 1 << Window.FEATURE_ACTION_MODE_OVERLAY;
- private static final int WINDOW_FLAG_INDETERMINANTE_PROGRESS = 1 << Window.FEATURE_INDETERMINATE_PROGRESS;
-
final SupportActivity.InternalCallbacks mInternalCallbacks = new SupportActivity.InternalCallbacks() {
@Override
void invalidateSupportFragmentIndex(int index) {
@@ -112,11 +114,6 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
return mFragments;
}
- @Override
- void ensureSupportActionBarAttached() {
- FragmentMapActivity.this.ensureSupportActionBarAttached();
- }
-
@Override
boolean getRetaining() {
return mRetaining;
@@ -143,16 +140,54 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
};
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
+ ViewGroup mDecor;
+ ViewGroup mContentParent;
ActionBar mActionBar;
- boolean mIsActionBarImplAttached;
+ ActionBarView mActionBarView;
long mWindowFlags = 0;
- final MenuBuilder mSupportMenu;
+ android.view.MenuInflater mMenuInflater;
+
+ MenuBuilder mSupportMenu;
final MenuBuilder.Callback mSupportMenuCallback = new MenuBuilder.Callback() {
@Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return FragmentMapActivity.this.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
}
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ // No-op
+ }
+ };
+ private final MenuPresenter.Callback mMenuPresenterCallback = new MenuPresenter.Callback() {
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+ };
+
+ /** Map between native options items and sherlock items (pre-3.0 only). */
+ private HashMap mNativeItemMap;
+ /** Native menu item callback which proxies to our callback. */
+ private final android.view.MenuItem.OnMenuItemClickListener mNativeItemListener = new android.view.MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(android.view.MenuItem item) {
+ if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item);
+
+ final MenuItemImpl sherlockItem = mNativeItemMap.get(item);
+ if (sherlockItem != null) {
+ sherlockItem.invoke();
+ } else {
+ Log.e(TAG, "Options item \"" + item + "\" not found in mapping");
+ }
+
+ return true; //Do not allow continuation of native handling
+ }
};
boolean mCreated;
@@ -188,17 +223,7 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
- public FragmentMapActivity() {
- super();
- if (IS_HONEYCOMB) {
- mActionBar = ActionBarWrapper.createFor(this);
- mSupportMenu = null; //Everything should be done natively
- } else {
- mSupportMenu = new MenuBuilder(this);
- mSupportMenu.setCallback(mSupportMenuCallback);
- }
- }
@Override
public SupportActivity.InternalCallbacks getInternalCallbacks() {
@@ -210,39 +235,124 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
return this;
}
- protected void ensureSupportActionBarAttached() {
- if (IS_HONEYCOMB || mIsActionBarImplAttached) {
+ private void initActionBar() {
+ if (DEBUG) Log.d(TAG, "[initActionBar]");
+
+ // Initializing the window decor can change window feature flags.
+ // Make sure that we have the correct set before performing the test below.
+ if (mDecor == null) {
+ installDecor();
+ }
+
+ if ((mActionBar != null) || !hasFeature(Window.FEATURE_ACTION_BAR) || isChild()) {
return;
}
- if (!isChild()) {
- if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR) == WINDOW_FLAG_ACTION_BAR) {
- if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_OVERLAY) == WINDOW_FLAG_ACTION_BAR_OVERLAY) {
- super.setContentView(R.layout.abs__screen_action_bar_overlay);
- } else {
- super.setContentView(R.layout.abs__screen_action_bar);
- }
- mActionBar = new ActionBarImpl(this);
- ((ActionBarImpl)mActionBar).init();
+ if (IS_HONEYCOMB) {
+ mActionBar = ActionBarWrapper.createFor(this);
+ } else {
+ mActionBar = new ActionBarImpl(this);
+ }
+ }
- final boolean textEnabled = ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_ITEM_TEXT) == WINDOW_FLAG_ACTION_BAR_ITEM_TEXT);
- mSupportMenu.setShowsActionItemText(textEnabled);
+ private void installDecor() {
+ if (DEBUG) Log.d(TAG, "[installDecor]");
- if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- ((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(false);
+ if (mDecor == null) {
+ if (IS_HONEYCOMB) {
+ mDecor = (ViewGroup)getWindow().getDecorView();
+ } else {
+ mDecor = (ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content);
+ }
+ }
+ if (mContentParent == null) {
+ if (IS_HONEYCOMB) {
+ mContentParent = (ViewGroup)mDecor.findViewById(android.R.id.content);
+ } else {
+ mContentParent = generateLayout();
+ mActionBarView = (ActionBarView)mDecor.findViewById(R.id.abs__action_bar);
+ if (mActionBarView != null) {
+ if (mActionBarView.getTitle() == null) {
+ mActionBarView.setTitle(getTitle());
+ }
+ if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) {
+ mActionBarView.initIndeterminateProgress();
+ }
}
- //TODO set other flags
+ // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ mDecor.post(new Runnable() {
+ @Override
+ public void run() {
+ //Invalidate if the panel menu hasn't been created before this.
+ if (mSupportMenu == null) {
+ invalidateOptionsMenu();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private ViewGroup generateLayout() {
+ if (DEBUG) Log.d(TAG, "[generateLayout]");
+
+ // Apply data from current theme.
+
+ TypedArray a = getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
+
+ if (a.getBoolean(R.styleable.SherlockTheme_windowNoTitle, false)) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } else if (a.getBoolean(R.styleable.SherlockTheme_windowActionBar, false)) {
+ // Don't allow an action bar if there is no title.
+ requestWindowFeature(Window.FEATURE_ACTION_BAR);
+ }
+
+ if (a.getBoolean(R.styleable.SherlockTheme_windowActionBarOverlay, false)) {
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false)) {
+ requestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
+ }
+
+ a.recycle();
+
+ int layoutResource;
+ if (hasFeature(Window.FEATURE_ACTION_BAR)) {
+ if (hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
+ layoutResource = R.layout.abs__screen_action_bar_overlay;
} else {
- if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- super.requestWindowFeature((int)Window.FEATURE_INDETERMINATE_PROGRESS);
- }
- super.setContentView(R.layout.abs__screen_simple);
+ layoutResource = R.layout.abs__screen_action_bar;
}
+ //} else if (hasFeature(Window.FEATURE_ACTION_MODE_OVERLAY)) {
+ // layoutResource = R.layout.abs__screen_simple_overlay_action_mode;
+ } else {
+ layoutResource = R.layout.abs__screen_simple;
}
- invalidateOptionsMenu();
- mIsActionBarImplAttached = true;
+ View in = getLayoutInflater().inflate(layoutResource, null);
+ mDecor.addView(in, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ ViewGroup contentParent = (ViewGroup)mDecor.findViewById(R.id.abs__content);
+ if (contentParent == null) {
+ throw new RuntimeException("Couldn't find content container view");
+ }
+
+ //Make our new child the true content view (for fragments). VERY VOLATILE!
+ mDecor.setId(View.NO_ID);
+ contentParent.setId(android.R.id.content);
+
+ return contentParent;
+ }
+
+ private boolean hasFeature(long featureId) {
+ if (IS_HONEYCOMB) {
+ return getWindow().hasFeature((int)featureId);
+ }
+ return (mWindowFlags & (1 << featureId)) != 0;
}
// ------------------------------------------------------------------------
@@ -262,12 +372,11 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
if (!IS_HONEYCOMB) {
switch ((int)featureId) {
case (int)Window.FEATURE_ACTION_BAR:
- case (int)Window.FEATURE_ACTION_BAR_ITEM_TEXT:
case (int)Window.FEATURE_ACTION_BAR_OVERLAY:
case (int)Window.FEATURE_ACTION_MODE_OVERLAY:
case (int)Window.FEATURE_INDETERMINATE_PROGRESS:
mWindowFlags |= (1 << featureId);
- return true;
+ return true;
}
}
return super.requestWindowFeature((int)featureId);
@@ -275,58 +384,86 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
@Override
public android.view.MenuInflater getMenuInflater() {
+ if (DEBUG) Log.d(TAG, "[getMenuInflater]");
+
+ if (mMenuInflater == null) {
+ initActionBar();
+ }
if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "getMenuInflater(): Wrapping native inflater.");
//Wrap the native inflater so it can unwrap the native menu first
- return new MenuInflaterWrapper(this, super.getMenuInflater());
- }
+ mMenuInflater = new MenuInflaterWrapper(this, super.getMenuInflater());
+ } else {
+ if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
- if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
+ //Use our custom menu inflater
+ mMenuInflater = new MenuInflaterImpl(this, super.getMenuInflater());
+ }
- //Use our custom menu inflater
- return new MenuInflater(this, super.getMenuInflater());
+ return mMenuInflater;
}
@Override
public void setContentView(int layoutResId) {
- ensureSupportActionBarAttached();
- if (IS_HONEYCOMB || isChild()) {
- super.setContentView(layoutResId);
+ if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId);
+
+ if (mContentParent == null) {
+ installDecor();
} else {
- FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
- contentView.removeAllViews();
- getLayoutInflater().inflate(layoutResId, contentView, true);
+ mContentParent.removeAllViews();
+ }
+ getLayoutInflater().inflate(layoutResId, mContentParent);
+
+ android.view.Window.Callback callback = getWindow().getCallback();
+ if (callback != null) {
+ callback.onContentChanged();
}
+ initActionBar();
}
@Override
public void setContentView(View view, LayoutParams params) {
- ensureSupportActionBarAttached();
- if (IS_HONEYCOMB || isChild()) {
- super.setContentView(view, params);
+ if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params);
+
+ if (mContentParent == null) {
+ installDecor();
} else {
- FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
- contentView.removeAllViews();
- contentView.addView(view, params);
+ mContentParent.removeAllViews();
}
+ mContentParent.addView(view, params);
+
+ android.view.Window.Callback callback = getWindow().getCallback();
+ if (callback != null) {
+ callback.onContentChanged();
+ }
+
+ initActionBar();
}
@Override
public void setContentView(View view) {
- ensureSupportActionBarAttached();
- if (IS_HONEYCOMB || isChild()) {
- super.setContentView(view);
- } else {
- FrameLayout contentView = (FrameLayout)findViewById(R.id.abs__content);
- contentView.removeAllViews();
- contentView.addView(view);
+ if (DEBUG) Log.d(TAG, "[setContentView] view: " + view);
+
+ setContentView(view, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params);
+
+ if (mContentParent == null) {
+ installDecor();
}
+ mContentParent.addView(view, params);
+
+ initActionBar();
}
@Override
public void setTitle(CharSequence title) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(title);
} else {
getSupportActionBar().setTitle(title);
@@ -335,7 +472,7 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
@Override
public void setTitle(int titleId) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(titleId);
} else {
getSupportActionBar().setTitle(titleId);
@@ -368,20 +505,6 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
super.onActivityResult(requestCode, resultCode, data);
}
- @Override
- protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
- TypedArray attrs = theme.obtainStyledAttributes(resid, R.styleable.SherlockTheme);
-
- final boolean actionBar = attrs.getBoolean(R.styleable.SherlockTheme_windowActionBar, false);
- mWindowFlags |= actionBar ? WINDOW_FLAG_ACTION_BAR : 0;
-
- final boolean actionModeOverlay = attrs.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false);
- mWindowFlags |= actionModeOverlay ? WINDOW_FLAG_ACTION_MODE_OVERLAY : 0;
-
- attrs.recycle();
- super.onApplyThemeResource(theme, resid, first);
- }
-
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
@@ -451,8 +574,8 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning " + menu.hasVisibleItems());
- return menu.hasVisibleItems();
+ if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning true");
+ return true;
}
@Override
@@ -473,6 +596,14 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
return result;
}
+ private boolean dispatchCreateOptionsMenu() {
+ if (DEBUG) Log.d(TAG, "[dispatchCreateOptionsMenu]");
+
+ boolean result = onCreateOptionsMenu(mSupportMenu);
+ result |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
+ return result;
+ }
+
/**
* Add support for inflating the <fragment> tag.
*/
@@ -558,29 +689,40 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
@Override
public void invalidateOptionsMenu() {
- if (DEBUG) Log.d(TAG, "supportInvalidateOptionsMenu(): Invalidating menu.");
+ if (DEBUG) Log.d(TAG, "[invalidateOptionsMenu]");
if (IS_HONEYCOMB) {
HoneycombInvalidateOptionsMenu.invoke(this);
- } else {
- mSupportMenu.clear();
+ return;
+ }
- mOptionsMenuCreateResult = onCreateOptionsMenu(mSupportMenu);
- mOptionsMenuCreateResult |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
+ if (mSupportMenu == null) {
+ mSupportMenu = new MenuBuilder(this);
+ mSupportMenu.setCallback(mSupportMenuCallback);
+ }
- if (getSupportActionBar() != null) {
- if (onPrepareOptionsMenu(mSupportMenu)) {
- mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
- }
+ mSupportMenu.stopDispatchingItemsChanged();
+ mSupportMenu.clear();
- //Since we now know we are using a custom action bar, perform the
- //inflation callback to allow it to display any items it wants.
- ((ActionBarImpl)mActionBar).onMenuInflated(mSupportMenu);
+ if (!dispatchCreateOptionsMenu()) {
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
}
+ return;
+ }
+
+ if (!dispatchPrepareOptionsMenu()) {
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
+ }
+ mSupportMenu.startDispatchingItemsChanged();
+ return;
+ }
+
+ mSupportMenu.startDispatchingItemsChanged();
- // Whoops, older platform... we'll use a hack, to manually rebuild
- // the options menu the next time it is prepared.
- mOptionsMenuInvalidated = true;
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(mSupportMenu, mMenuPresenterCallback);
}
}
@@ -736,68 +878,46 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- boolean result = menu.hasVisibleItems();
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(Menu): Returning " + result);
- return result;
+ return true;
}
@Override
public final boolean onPrepareOptionsMenu(android.view.Menu menu) {
- boolean result = super.onPrepareOptionsMenu(menu);
-
- if (!IS_HONEYCOMB) {
- if (DEBUG) {
- Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuCreateResult = " + mOptionsMenuCreateResult);
- Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuInvalidated = " + mOptionsMenuInvalidated);
- }
-
- boolean prepareResult = true;
- if (mOptionsMenuCreateResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with custom menu.");
- prepareResult = onPrepareOptionsMenu(mSupportMenu);
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Support method result returned " + prepareResult);
- if (prepareResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with custom menu.");
- mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
- }
- }
-
- if (mOptionsMenuInvalidated) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Clearing existing options menu.");
- menu.clear();
- mOptionsMenuInvalidated = false;
-
- if (mOptionsMenuCreateResult && prepareResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Adding any action items that are not displayed on the action bar.");
- //Only add items that have not already been added to our custom
- //action bar implementation
- for (MenuItemImpl item : mSupportMenu.getItems()) {
- if (!item.isShownOnActionBar()) {
- item.addTo(menu);
- }
- }
- }
- }
-
- if (mOptionsMenuCreateResult && prepareResult && menu.hasVisibleItems()) {
- if (getSupportActionBar() != null) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatch menu visibility true to custom action bar.");
- ((ActionBarImpl)mActionBar).onMenuVisibilityChanged(true);
- }
- result = true;
- }
- } else {
+ if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with wrapped native menu.");
final MenuWrapper wrappedMenu = new MenuWrapper(menu);
- result = onPrepareOptionsMenu(wrappedMenu);
+ boolean result = onPrepareOptionsMenu(wrappedMenu);
if (result) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with wrapped native menu.");
mFragments.dispatchPrepareOptionsMenu(wrappedMenu);
}
+ return result;
}
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Returning " + result);
- return result;
+ if (!dispatchPrepareOptionsMenu()) {
+ return false;
+ }
+
+ if (mNativeItemMap == null) {
+ mNativeItemMap = new HashMap();
+ } else {
+ mNativeItemMap.clear();
+ }
+
+ if (mSupportMenu != null) {
+ return mSupportMenu.bindOverflowToNative(menu, mNativeItemListener, mNativeItemMap);
+ }
+ return false;
+ }
+
+ private boolean dispatchPrepareOptionsMenu() {
+ if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu]");
+
+ if (onPrepareOptionsMenu(mSupportMenu)) {
+ mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
+ return true;
+ }
+ return false;
}
/**
@@ -904,7 +1024,6 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
if (!mCreated) {
mCreated = true;
- ensureSupportActionBarAttached(); //Needed for retained fragments
mFragments.dispatchActivityCreated();
}
@@ -961,10 +1080,10 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
*/
@Override
public void setProgressBarIndeterminateVisibility(Boolean visible) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setProgressBarIndeterminateVisibility(visible);
- } else if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- ((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(visible);
+ } else {
+ mActionBarView.setProgressBarIndeterminateVisibility(visible);
}
}
@@ -1076,7 +1195,8 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
*/
@Override
public ActionBar getSupportActionBar() {
- return (mActionBar != null) ? mActionBar.getPublicInstance() : null;
+ initActionBar();
+ return mActionBar;
}
/**
@@ -1163,6 +1283,8 @@ public abstract class FragmentMapActivity extends MapActivity implements Support
*/
@Override
public FragmentManager getSupportFragmentManager() {
+ //PLEASE let no one be dumb enough to call this too soon...
+ initActionBar();
return mFragments;
}
diff --git a/actionbarsherlock/plugins/pom.xml b/actionbarsherlock/plugins/pom.xml
index b9957d6a4..f5ea7faee 100644
--- a/actionbarsherlock/plugins/pom.xml
+++ b/actionbarsherlock/plugins/pom.xml
@@ -11,7 +11,7 @@
com.actionbarsherlock
parent
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/plugins/preference/AndroidManifest.xml b/actionbarsherlock/plugins/preference/AndroidManifest.xml
index 8615e26fa..7b80ceee3 100644
--- a/actionbarsherlock/plugins/preference/AndroidManifest.xml
+++ b/actionbarsherlock/plugins/preference/AndroidManifest.xml
@@ -3,8 +3,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-plugins
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/plugins/preference/src/android/support/v4/app/SherlockPreferenceActivity.java b/actionbarsherlock/plugins/preference/src/android/support/v4/app/SherlockPreferenceActivity.java
index c2ab8c5c2..a3c4e4ce8 100644
--- a/actionbarsherlock/plugins/preference/src/android/support/v4/app/SherlockPreferenceActivity.java
+++ b/actionbarsherlock/plugins/preference/src/android/support/v4/app/SherlockPreferenceActivity.java
@@ -21,19 +21,10 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.internal.app.ActionBarImpl;
-import com.actionbarsherlock.internal.app.ActionBarWrapper;
-import com.actionbarsherlock.internal.view.menu.MenuBuilder;
-import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
-import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
-import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
-import com.actionbarsherlock.internal.view.menu.MenuWrapper;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
-import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
@@ -43,7 +34,6 @@ import android.os.Parcelable;
import android.preference.PreferenceActivity;
import android.support.v4.view.ActionMode;
import android.support.v4.view.Menu;
-import android.support.v4.view.MenuInflater;
import android.support.v4.view.MenuItem;
import android.support.v4.view.Window;
import android.util.AttributeSet;
@@ -51,11 +41,29 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ListView;
+import android.view.ViewGroup.LayoutParams;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.app.ActionBarImpl;
+import com.actionbarsherlock.internal.app.ActionBarWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuBuilder;
+import com.actionbarsherlock.internal.view.menu.MenuInflaterImpl;
+import com.actionbarsherlock.internal.view.menu.MenuInflaterWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
+import com.actionbarsherlock.internal.view.menu.MenuItemWrapper;
+import com.actionbarsherlock.internal.view.menu.MenuPresenter;
+import com.actionbarsherlock.internal.view.menu.MenuWrapper;
+import com.actionbarsherlock.internal.widget.ActionBarView;
/**
- * Base class for activities that want to use the support-based ActionBar,
- * Fragment, Loader, and Preference APIs.
+ * Base class for activities that want to use the support-based
+ * {@link android.support.v4.app.Fragment},
+ * {@link android.support.v4.content.Loader},
+ * {@link android.support.v4.app.ActionBar}, and Preference APIs.
+ *
+ * When using this class as opposed to new platform's built-in fragment
+ * and loader support, you must use the {@link #getSupportFragmentManager()}
+ * and {@link #getSupportLoaderManager()} methods respectively to access
+ * those features.
*
*
Known limitations:
*
@@ -70,7 +78,7 @@ import android.widget.ListView;
* prior to Honeycomb, where the state is saved before pausing. To address this,
* when running on platforms prior to Honeycomb an exception will not be thrown
* if you change fragments between the state save and the activity being stopped.
- * This means that is some cases if the activity is restored from its last saved
+ * This means that in some cases if the activity is restored from its last saved
* state, this may be a snapshot slightly before what the user last saw.
*
*/
@@ -85,12 +93,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
static final int MSG_REALLY_STOPPED = 1;
static final int MSG_RESUME_PENDING = 2;
- private static final int WINDOW_FLAG_ACTION_BAR = 1 << Window.FEATURE_ACTION_BAR;
- private static final int WINDOW_FLAG_ACTION_BAR_ITEM_TEXT = 1 << Window.FEATURE_ACTION_BAR_ITEM_TEXT;
- private static final int WINDOW_FLAG_ACTION_BAR_OVERLAY = 1 << Window.FEATURE_ACTION_BAR_OVERLAY;
- private static final int WINDOW_FLAG_ACTION_MODE_OVERLAY = 1 << Window.FEATURE_ACTION_MODE_OVERLAY;
- private static final int WINDOW_FLAG_INDETERMINANTE_PROGRESS = 1 << Window.FEATURE_INDETERMINATE_PROGRESS;
-
final SupportActivity.InternalCallbacks mInternalCallbacks = new SupportActivity.InternalCallbacks() {
@Override
void invalidateSupportFragmentIndex(int index) {
@@ -112,11 +114,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
return mFragments;
}
- @Override
- void ensureSupportActionBarAttached() {
- SherlockPreferenceActivity.this.ensureSupportActionBarAttached();
- }
-
@Override
boolean getRetaining() {
return mRetaining;
@@ -143,16 +140,54 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
};
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
+ ViewGroup mDecor;
+ ViewGroup mContentParent;
ActionBar mActionBar;
- boolean mIsActionBarImplAttached;
+ ActionBarView mActionBarView;
long mWindowFlags = 0;
- final MenuBuilder mSupportMenu;
+ android.view.MenuInflater mMenuInflater;
+
+ MenuBuilder mSupportMenu;
final MenuBuilder.Callback mSupportMenuCallback = new MenuBuilder.Callback() {
@Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
return SherlockPreferenceActivity.this.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
}
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ // No-op
+ }
+ };
+ private final MenuPresenter.Callback mMenuPresenterCallback = new MenuPresenter.Callback() {
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+ };
+
+ /** Map between native options items and sherlock items (pre-3.0 only). */
+ private HashMap mNativeItemMap;
+ /** Native menu item callback which proxies to our callback. */
+ private final android.view.MenuItem.OnMenuItemClickListener mNativeItemListener = new android.view.MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(android.view.MenuItem item) {
+ if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item);
+
+ final MenuItemImpl sherlockItem = mNativeItemMap.get(item);
+ if (sherlockItem != null) {
+ sherlockItem.invoke();
+ } else {
+ Log.e(TAG, "Options item \"" + item + "\" not found in mapping");
+ }
+
+ return true; //Do not allow continuation of native handling
+ }
};
boolean mCreated;
@@ -161,8 +196,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
boolean mReallyStopped;
boolean mRetaining;
- boolean mIsEnsureInflating = false;
-
boolean mOptionsMenuInvalidated;
boolean mOptionsMenuCreateResult;
@@ -190,17 +223,7 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
- public SherlockPreferenceActivity() {
- super();
- if (IS_HONEYCOMB) {
- mActionBar = ActionBarWrapper.createFor(this);
- mSupportMenu = null; //Everything should be done natively
- } else {
- mSupportMenu = new MenuBuilder(this);
- mSupportMenu.setCallback(mSupportMenuCallback);
- }
- }
@Override
public SupportActivity.InternalCallbacks getInternalCallbacks() {
@@ -212,60 +235,124 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
return this;
}
- protected void ensureSupportActionBarAttached() {
- if (IS_HONEYCOMB || mIsActionBarImplAttached) {
+ private void initActionBar() {
+ if (DEBUG) Log.d(TAG, "[initActionBar]");
+
+ // Initializing the window decor can change window feature flags.
+ // Make sure that we have the correct set before performing the test below.
+ if (mDecor == null) {
+ installDecor();
+ }
+
+ if ((mActionBar != null) || !hasFeature(Window.FEATURE_ACTION_BAR) || isChild()) {
return;
}
- if (!isChild()) {
- //Get the list view that the parent attached
- final ListView contentView = (ListView)getWindow().getDecorView().findViewById(android.R.id.list);
- ((ViewGroup)contentView.getParent()).removeView(contentView);
- //Disable the callback in PrefrenceActivity
- mIsEnsureInflating = true;
+ if (IS_HONEYCOMB) {
+ mActionBar = ActionBarWrapper.createFor(this);
+ } else {
+ mActionBar = new ActionBarImpl(this);
+ }
+ }
- if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR) == WINDOW_FLAG_ACTION_BAR) {
- if ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_OVERLAY) == WINDOW_FLAG_ACTION_BAR_OVERLAY) {
- super.setContentView(R.layout.abs__screen_action_bar_overlay);
- } else {
- super.setContentView(R.layout.abs__screen_action_bar);
+ private void installDecor() {
+ if (DEBUG) Log.d(TAG, "[installDecor]");
+
+ if (mDecor == null) {
+ if (IS_HONEYCOMB) {
+ mDecor = (ViewGroup)getWindow().getDecorView();
+ } else {
+ mDecor = (ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content);
+ }
+ }
+ if (mContentParent == null) {
+ if (IS_HONEYCOMB) {
+ mContentParent = (ViewGroup)mDecor.findViewById(android.R.id.content);
+ } else {
+ mContentParent = generateLayout();
+ mActionBarView = (ActionBarView)mDecor.findViewById(R.id.abs__action_bar);
+ if (mActionBarView != null) {
+ if (mActionBarView.getTitle() == null) {
+ mActionBarView.setTitle(getTitle());
+ }
+ if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) {
+ mActionBarView.initIndeterminateProgress();
+ }
}
- mActionBar = new ActionBarImpl(this);
- ((ActionBarImpl)mActionBar).init();
+ // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ mDecor.post(new Runnable() {
+ @Override
+ public void run() {
+ //Invalidate if the panel menu hasn't been created before this.
+ if (mSupportMenu == null) {
+ invalidateOptionsMenu();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private ViewGroup generateLayout() {
+ if (DEBUG) Log.d(TAG, "[generateLayout]");
- final boolean textEnabled = ((mWindowFlags & WINDOW_FLAG_ACTION_BAR_ITEM_TEXT) == WINDOW_FLAG_ACTION_BAR_ITEM_TEXT);
- mSupportMenu.setShowsActionItemText(textEnabled);
+ // Apply data from current theme.
- if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- ((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(false);
- }
+ TypedArray a = getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
- //TODO set other flags
+ if (a.getBoolean(R.styleable.SherlockTheme_windowNoTitle, false)) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } else if (a.getBoolean(R.styleable.SherlockTheme_windowActionBar, false)) {
+ // Don't allow an action bar if there is no title.
+ requestWindowFeature(Window.FEATURE_ACTION_BAR);
+ }
+
+ if (a.getBoolean(R.styleable.SherlockTheme_windowActionBarOverlay, false)) {
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false)) {
+ requestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
+ }
+
+ a.recycle();
+
+ int layoutResource;
+ if (hasFeature(Window.FEATURE_ACTION_BAR)) {
+ if (hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
+ layoutResource = R.layout.abs__screen_action_bar_overlay;
} else {
- if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- super.requestWindowFeature((int)Window.FEATURE_INDETERMINATE_PROGRESS);
- }
- super.setContentView(R.layout.abs__screen_simple);
+ layoutResource = R.layout.abs__screen_action_bar;
}
+ //} else if (hasFeature(Window.FEATURE_ACTION_MODE_OVERLAY)) {
+ // layoutResource = R.layout.abs__screen_simple_overlay_action_mode;
+ } else {
+ layoutResource = R.layout.abs__screen_simple;
+ }
- //Attach original list view to the new layout
- ((ViewGroup)findViewById(R.id.abs__content)).addView(contentView);
+ View in = getLayoutInflater().inflate(layoutResource, null);
+ mDecor.addView(in, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- //Re-enable the callback in PreferenceActivity
- mIsEnsureInflating = false;
- super.onContentChanged();
+ ViewGroup contentParent = (ViewGroup)mDecor.findViewById(R.id.abs__content);
+ if (contentParent == null) {
+ throw new RuntimeException("Couldn't find content container view");
}
- invalidateOptionsMenu();
- mIsActionBarImplAttached = true;
+ //Make our new child the true content view (for fragments). VERY VOLATILE!
+ mDecor.setId(View.NO_ID);
+ contentParent.setId(android.R.id.content);
+
+ return contentParent;
}
- @Override
- public void onContentChanged() {
- if (!mIsEnsureInflating) {
- super.onContentChanged();
+ private boolean hasFeature(long featureId) {
+ if (IS_HONEYCOMB) {
+ return getWindow().hasFeature((int)featureId);
}
+ return (mWindowFlags & (1 << featureId)) != 0;
}
// ------------------------------------------------------------------------
@@ -285,12 +372,11 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
if (!IS_HONEYCOMB) {
switch ((int)featureId) {
case (int)Window.FEATURE_ACTION_BAR:
- case (int)Window.FEATURE_ACTION_BAR_ITEM_TEXT:
case (int)Window.FEATURE_ACTION_BAR_OVERLAY:
case (int)Window.FEATURE_ACTION_MODE_OVERLAY:
case (int)Window.FEATURE_INDETERMINATE_PROGRESS:
mWindowFlags |= (1 << featureId);
- return true;
+ return true;
}
}
return super.requestWindowFeature((int)featureId);
@@ -298,22 +384,86 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
@Override
public android.view.MenuInflater getMenuInflater() {
+ if (DEBUG) Log.d(TAG, "[getMenuInflater]");
+
+ if (mMenuInflater == null) {
+ initActionBar();
+ }
if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "getMenuInflater(): Wrapping native inflater.");
//Wrap the native inflater so it can unwrap the native menu first
- return new MenuInflaterWrapper(this, super.getMenuInflater());
+ mMenuInflater = new MenuInflaterWrapper(this, super.getMenuInflater());
+ } else {
+ if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
+
+ //Use our custom menu inflater
+ mMenuInflater = new MenuInflaterImpl(this, super.getMenuInflater());
+ }
+
+ return mMenuInflater;
+ }
+
+ @Override
+ public void setContentView(int layoutResId) {
+ if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId);
+
+ if (mContentParent == null) {
+ installDecor();
+ } else {
+ mContentParent.removeAllViews();
+ }
+ getLayoutInflater().inflate(layoutResId, mContentParent);
+
+ android.view.Window.Callback callback = getWindow().getCallback();
+ if (callback != null) {
+ callback.onContentChanged();
+ }
+ initActionBar();
+ }
+
+ @Override
+ public void setContentView(View view, LayoutParams params) {
+ if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params);
+
+ if (mContentParent == null) {
+ installDecor();
+ } else {
+ mContentParent.removeAllViews();
+ }
+ mContentParent.addView(view, params);
+
+ android.view.Window.Callback callback = getWindow().getCallback();
+ if (callback != null) {
+ callback.onContentChanged();
}
- if (DEBUG) Log.d(TAG, "getMenuInflater(): Returning support inflater.");
+ initActionBar();
+ }
+
+ @Override
+ public void setContentView(View view) {
+ if (DEBUG) Log.d(TAG, "[setContentView] view: " + view);
- //Use our custom menu inflater
- return new MenuInflater(this, super.getMenuInflater());
+ setContentView(view, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params);
+
+ if (mContentParent == null) {
+ installDecor();
+ }
+ mContentParent.addView(view, params);
+
+ initActionBar();
}
@Override
public void setTitle(CharSequence title) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(title);
} else {
getSupportActionBar().setTitle(title);
@@ -322,7 +472,7 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
@Override
public void setTitle(int titleId) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setTitle(titleId);
} else {
getSupportActionBar().setTitle(titleId);
@@ -355,20 +505,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
super.onActivityResult(requestCode, resultCode, data);
}
- @Override
- protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
- TypedArray attrs = theme.obtainStyledAttributes(resid, R.styleable.SherlockTheme);
-
- final boolean actionBar = attrs.getBoolean(R.styleable.SherlockTheme_windowActionBar, false);
- mWindowFlags |= actionBar ? WINDOW_FLAG_ACTION_BAR : 0;
-
- final boolean actionModeOverlay = attrs.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false);
- mWindowFlags |= actionModeOverlay ? WINDOW_FLAG_ACTION_MODE_OVERLAY : 0;
-
- attrs.recycle();
- super.onApplyThemeResource(theme, resid, first);
- }
-
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
@@ -411,7 +547,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
}
- ensureSupportActionBarAttached();
mFragments.dispatchCreate();
}
@@ -439,8 +574,8 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning " + menu.hasVisibleItems());
- return menu.hasVisibleItems();
+ if (DEBUG) Log.d(TAG, "onCreateOptionsMenu(Menu): Returning true");
+ return true;
}
@Override
@@ -461,6 +596,14 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
return result;
}
+ private boolean dispatchCreateOptionsMenu() {
+ if (DEBUG) Log.d(TAG, "[dispatchCreateOptionsMenu]");
+
+ boolean result = onCreateOptionsMenu(mSupportMenu);
+ result |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
+ return result;
+ }
+
/**
* Add support for inflating the <fragment> tag.
*/
@@ -546,29 +689,40 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
@Override
public void invalidateOptionsMenu() {
- if (DEBUG) Log.d(TAG, "supportInvalidateOptionsMenu(): Invalidating menu.");
+ if (DEBUG) Log.d(TAG, "[invalidateOptionsMenu]");
if (IS_HONEYCOMB) {
HoneycombInvalidateOptionsMenu.invoke(this);
- } else {
- mSupportMenu.clear();
+ return;
+ }
- mOptionsMenuCreateResult = onCreateOptionsMenu(mSupportMenu);
- mOptionsMenuCreateResult |= mFragments.dispatchCreateOptionsMenu(mSupportMenu, getMenuInflater());
+ if (mSupportMenu == null) {
+ mSupportMenu = new MenuBuilder(this);
+ mSupportMenu.setCallback(mSupportMenuCallback);
+ }
- if (getSupportActionBar() != null) {
- if (onPrepareOptionsMenu(mSupportMenu)) {
- mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
- }
+ mSupportMenu.stopDispatchingItemsChanged();
+ mSupportMenu.clear();
- //Since we now know we are using a custom action bar, perform the
- //inflation callback to allow it to display any items it wants.
- ((ActionBarImpl)mActionBar).onMenuInflated(mSupportMenu);
+ if (!dispatchCreateOptionsMenu()) {
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
}
+ return;
+ }
- // Whoops, older platform... we'll use a hack, to manually rebuild
- // the options menu the next time it is prepared.
- mOptionsMenuInvalidated = true;
+ if (!dispatchPrepareOptionsMenu()) {
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(null, mMenuPresenterCallback);
+ }
+ mSupportMenu.startDispatchingItemsChanged();
+ return;
+ }
+
+ mSupportMenu.startDispatchingItemsChanged();
+
+ if (mActionBar != null) {
+ ((ActionBarImpl)mActionBar).setMenu(mSupportMenu, mMenuPresenterCallback);
}
}
@@ -724,68 +878,46 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- boolean result = menu.hasVisibleItems();
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(Menu): Returning " + result);
- return result;
+ return true;
}
@Override
public final boolean onPrepareOptionsMenu(android.view.Menu menu) {
- boolean result = super.onPrepareOptionsMenu(menu);
-
- if (!IS_HONEYCOMB) {
- if (DEBUG) {
- Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuCreateResult = " + mOptionsMenuCreateResult);
- Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): mOptionsMenuInvalidated = " + mOptionsMenuInvalidated);
- }
-
- boolean prepareResult = true;
- if (mOptionsMenuCreateResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with custom menu.");
- prepareResult = onPrepareOptionsMenu(mSupportMenu);
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Support method result returned " + prepareResult);
- if (prepareResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with custom menu.");
- mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
- }
- }
-
- if (mOptionsMenuInvalidated) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Clearing existing options menu.");
- menu.clear();
- mOptionsMenuInvalidated = false;
-
- if (mOptionsMenuCreateResult && prepareResult) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Adding any action items that are not displayed on the action bar.");
- //Only add items that have not already been added to our custom
- //action bar implementation
- for (MenuItemImpl item : mSupportMenu.getItems()) {
- if (!item.isShownOnActionBar()) {
- item.addTo(menu);
- }
- }
- }
- }
-
- if (mOptionsMenuCreateResult && prepareResult && menu.hasVisibleItems()) {
- if (getSupportActionBar() != null) {
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatch menu visibility true to custom action bar.");
- ((ActionBarImpl)mActionBar).onMenuVisibilityChanged(true);
- }
- result = true;
- }
- } else {
+ if (IS_HONEYCOMB) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Calling support method with wrapped native menu.");
final MenuWrapper wrappedMenu = new MenuWrapper(menu);
- result = onPrepareOptionsMenu(wrappedMenu);
+ boolean result = onPrepareOptionsMenu(wrappedMenu);
if (result) {
if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Dispatching fragment method with wrapped native menu.");
mFragments.dispatchPrepareOptionsMenu(wrappedMenu);
}
+ return result;
}
- if (DEBUG) Log.d(TAG, "onPrepareOptionsMenu(android.view.Menu): Returning " + result);
- return result;
+ if (!dispatchPrepareOptionsMenu()) {
+ return false;
+ }
+
+ if (mNativeItemMap == null) {
+ mNativeItemMap = new HashMap();
+ } else {
+ mNativeItemMap.clear();
+ }
+
+ if (mSupportMenu != null) {
+ return mSupportMenu.bindOverflowToNative(menu, mNativeItemListener, mNativeItemMap);
+ }
+ return false;
+ }
+
+ private boolean dispatchPrepareOptionsMenu() {
+ if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu]");
+
+ if (onPrepareOptionsMenu(mSupportMenu)) {
+ mFragments.dispatchPrepareOptionsMenu(mSupportMenu);
+ return true;
+ }
+ return false;
}
/**
@@ -892,7 +1024,6 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
if (!mCreated) {
mCreated = true;
- ensureSupportActionBarAttached(); //Needed for retained fragments
mFragments.dispatchActivityCreated();
}
@@ -949,10 +1080,10 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
*/
@Override
public void setProgressBarIndeterminateVisibility(Boolean visible) {
- if (IS_HONEYCOMB || (getSupportActionBar() == null)) {
+ if (IS_HONEYCOMB || (mActionBar == null)) {
super.setProgressBarIndeterminateVisibility(visible);
- } else if ((mWindowFlags & WINDOW_FLAG_INDETERMINANTE_PROGRESS) == WINDOW_FLAG_INDETERMINANTE_PROGRESS) {
- ((ActionBarImpl)mActionBar).setProgressBarIndeterminateVisibility(visible);
+ } else {
+ mActionBarView.setProgressBarIndeterminateVisibility(visible);
}
}
@@ -1064,7 +1195,8 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
*/
@Override
public ActionBar getSupportActionBar() {
- return (mActionBar != null) ? mActionBar.getPublicInstance() : null;
+ initActionBar();
+ return mActionBar;
}
/**
@@ -1151,6 +1283,8 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
*/
@Override
public FragmentManager getSupportFragmentManager() {
+ //PLEASE let no one be dumb enough to call this too soon...
+ initActionBar();
return mFragments;
}
diff --git a/actionbarsherlock/pom.xml b/actionbarsherlock/pom.xml
index 99c923738..6c0f59ffd 100644
--- a/actionbarsherlock/pom.xml
+++ b/actionbarsherlock/pom.xml
@@ -6,7 +6,7 @@
com.actionbarsherlock
parent
pom
- 3.4.2
+ 3.5.0
ActionBarSherlock (Parent)
Android library for implementing the action bar design pattern using the native ActionBar on 3.0+ and a custom implementation on pre-3.0 all through the same API.
diff --git a/actionbarsherlock/samples/demos/AndroidManifest.xml b/actionbarsherlock/samples/demos/AndroidManifest.xml
index 29102b29a..8aee08a1c 100644
--- a/actionbarsherlock/samples/demos/AndroidManifest.xml
+++ b/actionbarsherlock/samples/demos/AndroidManifest.xml
@@ -21,8 +21,8 @@
to come from a domain that you own or have control over. -->
+ android:versionCode="50"
+ android:versionName="3.5.0">
diff --git a/actionbarsherlock/samples/demos/pom.xml b/actionbarsherlock/samples/demos/pom.xml
index 4af0c4f30..9de24e4b7 100644
--- a/actionbarsherlock/samples/demos/pom.xml
+++ b/actionbarsherlock/samples/demos/pom.xml
@@ -11,7 +11,7 @@
com.actionbarsherlock
parent-sample
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarActionItemText.java b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarActionItemText.java
index 69e635dc5..fdb2ab8f9 100644
--- a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarActionItemText.java
+++ b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarActionItemText.java
@@ -19,7 +19,6 @@ import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
-import android.support.v4.view.Window;
import android.widget.TextView;
import com.actionbarsherlock.sample.demos.R;
@@ -40,7 +39,6 @@ public class ActionBarActionItemText extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_ACTION_BAR_ITEM_TEXT);
setContentView(R.layout.actionbar_text);
((TextView)findViewById(R.id.text)).setText(R.string.actionbar_actionitemtext_content);
}
diff --git a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarListNavigation.java b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarListNavigation.java
index 74caf8cd1..44b41f12f 100644
--- a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarListNavigation.java
+++ b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarListNavigation.java
@@ -11,14 +11,6 @@ public class ActionBarListNavigation extends FragmentActivity implements ActionB
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- //WARNING: This should normally not be needed as calling setContentView
- //or attaching a fragment to android.R.id.content will call this. In
- //this case, however, we call it manually since initializing the list
- //navigation will trigger a navigation changed callback and thus attach
- //the default fragment as the content.
- ensureSupportActionBarAttached();
-
-
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ArrayAdapter list = ArrayAdapter.createFromResource(this, R.array.locations, R.layout.abs__simple_spinner_item);
diff --git a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarPagerFragmentMenus.java b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarPagerFragmentMenus.java
index a8376e1d1..89de01444 100644
--- a/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarPagerFragmentMenus.java
+++ b/actionbarsherlock/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarPagerFragmentMenus.java
@@ -1,7 +1,7 @@
package com.actionbarsherlock.sample.demos.app;
import java.util.Random;
-import com.actionbarsherlock.sample.demos.R;
+
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@@ -10,7 +10,6 @@ import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
import android.support.v4.view.ViewPager;
-import android.support.v4.view.Window;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuInflater;
@@ -18,6 +17,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.actionbarsherlock.sample.demos.R;
+
public class ActionBarPagerFragmentMenus extends FragmentActivity {
private static final Random RANDOM = new Random();
private static final int PAGES = 10;
@@ -28,7 +29,6 @@ public class ActionBarPagerFragmentMenus extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_ACTION_BAR_ITEM_TEXT);
setContentView(R.layout.actionbar_pagerfragmentmenus);
mPager = (ViewPager)findViewById(R.id.pager);
diff --git a/actionbarsherlock/samples/plugins/AndroidManifest.xml b/actionbarsherlock/samples/plugins/AndroidManifest.xml
index 189e92769..c2d32875e 100644
--- a/actionbarsherlock/samples/plugins/AndroidManifest.xml
+++ b/actionbarsherlock/samples/plugins/AndroidManifest.xml
@@ -2,8 +2,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
diff --git a/actionbarsherlock/samples/plugins/pom.xml b/actionbarsherlock/samples/plugins/pom.xml
index 6a42052f0..819eb3413 100644
--- a/actionbarsherlock/samples/plugins/pom.xml
+++ b/actionbarsherlock/samples/plugins/pom.xml
@@ -11,7 +11,7 @@
com.actionbarsherlock
parent-sample
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/samples/pom.xml b/actionbarsherlock/samples/pom.xml
index 9ddb6da3e..9c07af191 100644
--- a/actionbarsherlock/samples/pom.xml
+++ b/actionbarsherlock/samples/pom.xml
@@ -11,7 +11,7 @@
com.actionbarsherlock
parent
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/samples/shakespeare/AndroidManifest.xml b/actionbarsherlock/samples/shakespeare/AndroidManifest.xml
index 69fa3bcb9..dc6e876a6 100644
--- a/actionbarsherlock/samples/shakespeare/AndroidManifest.xml
+++ b/actionbarsherlock/samples/shakespeare/AndroidManifest.xml
@@ -3,8 +3,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-sample
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/samples/styled/AndroidManifest.xml b/actionbarsherlock/samples/styled/AndroidManifest.xml
index f5b398484..0d8253381 100644
--- a/actionbarsherlock/samples/styled/AndroidManifest.xml
+++ b/actionbarsherlock/samples/styled/AndroidManifest.xml
@@ -17,8 +17,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-sample
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/test/app/AndroidManifest.xml b/actionbarsherlock/test/app/AndroidManifest.xml
index ec871bde4..7cba89359 100644
--- a/actionbarsherlock/test/app/AndroidManifest.xml
+++ b/actionbarsherlock/test/app/AndroidManifest.xml
@@ -2,8 +2,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-test
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/FeatureEnableActionItemText.java b/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/FeatureEnableActionItemText.java
deleted file mode 100644
index 9e2a5a043..000000000
--- a/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/FeatureEnableActionItemText.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.actionbarsherlock.tests.app;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.view.Menu;
-import android.support.v4.view.MenuItem;
-import android.support.v4.view.Window;
-
-public final class FeatureEnableActionItemText extends FragmentActivity {
- public static final String MENU_ITEM_TEXT = "Item";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_ACTION_BAR_ITEM_TEXT);
- setContentView(R.layout.blank);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(MENU_ITEM_TEXT).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- return true;
- }
-}
diff --git a/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/Issue0002.java b/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/Issue0002.java
index 5ac2e1686..3efb8fc34 100644
--- a/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/Issue0002.java
+++ b/actionbarsherlock/test/app/src/com/actionbarsherlock/tests/app/Issue0002.java
@@ -5,7 +5,6 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.Menu;
import android.support.v4.view.MenuItem;
-import android.support.v4.view.Window;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
@@ -20,7 +19,6 @@ public final class Issue0002 extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_ACTION_BAR_ITEM_TEXT);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, new TestFragment())
diff --git a/actionbarsherlock/test/pom.xml b/actionbarsherlock/test/pom.xml
index 179b4dcfb..7f2d9a59f 100644
--- a/actionbarsherlock/test/pom.xml
+++ b/actionbarsherlock/test/pom.xml
@@ -11,7 +11,7 @@
com.actionbarsherlock
parent
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/test/runner/AndroidManifest.xml b/actionbarsherlock/test/runner/AndroidManifest.xml
index 01aea3af3..35fc4a84b 100644
--- a/actionbarsherlock/test/runner/AndroidManifest.xml
+++ b/actionbarsherlock/test/runner/AndroidManifest.xml
@@ -2,8 +2,8 @@
+ android:versionCode="50"
+ android:versionName="3.5.0">
com.actionbarsherlock
parent-test
- 3.4.2
+ 3.5.0
../pom.xml
diff --git a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/BaseTestCase.java b/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/BaseTestCase.java
index 8dc67f7e6..78bc75256 100644
--- a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/BaseTestCase.java
+++ b/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/BaseTestCase.java
@@ -71,7 +71,7 @@ public abstract class BaseTestCase extends ActivityInstrumen
} else {
items = findViewsByClassName(getActivity().getWindow().getDecorView(), "com.actionbarsherlock.internal.view.menu.ActionMenuItemView");
for (View item : items) {
- TextView textView = (TextView)findViewByClassName(item, "android.widget.TextView");
+ TextView textView = (TextView)findViewByClassName(item, "android.widget.Button");
if (textView != null && text.equals(textView.getText())) {
return item;
}
diff --git a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestFeatureEnableActionItemText.java b/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestFeatureEnableActionItemText.java
deleted file mode 100644
index 7dbdd19a3..000000000
--- a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestFeatureEnableActionItemText.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.actionbarsherlock.tests.runner;
-
-import com.actionbarsherlock.tests.app.FeatureEnableActionItemText;
-import android.test.suitebuilder.annotation.Smoke;
-
-public class TestFeatureEnableActionItemText extends BaseTestCase {
- public TestFeatureEnableActionItemText() {
- super(FeatureEnableActionItemText.class);
- }
-
- @Smoke
- public void testFragmentReceivesOnMenuItemSelectedCallback() {
- if (IS_HONEYCOMB) {
- return;
- }
-
- assertNotNull("Text-only action-item could not be found.", findActionItem(FeatureEnableActionItemText.MENU_ITEM_TEXT));
- }
-}
diff --git a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestIssue0033.java b/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestIssue0033.java
index 31426e6aa..115be9e80 100644
--- a/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestIssue0033.java
+++ b/actionbarsherlock/test/runner/src/com/actionbarsherlock/tests/runner/TestIssue0033.java
@@ -32,7 +32,6 @@ public class TestIssue0033 extends BaseTestCase {
assertFalse(nativeItem.isVisible());
assertFalse(getInstrumentation().invokeMenuActionSync(getActivity(), Issue0033.ID_VISIBLE, 0));
assertFalse(actionItem.isVisible());
- assertEquals(View.GONE, actionItemView.getVisibility());
}
public void testHiddenMenuItems() throws InterruptedException {
@@ -60,10 +59,8 @@ public class TestIssue0033 extends BaseTestCase {
//Test action items are not present
View codeView = findActionItem(Issue0033.TEXT_HIDDEN);
- assertNotNull(codeView);
- assertEquals(View.GONE, codeView.getVisibility());
+ assertNull(codeView);
View xmlView = findActionItem(getActivity().getXmlHiddenText());
- assertNotNull(xmlView);
- assertEquals(View.GONE, xmlView.getVisibility());
+ assertNull(xmlView);
}
}
diff --git a/actionbarsherlock/website/resources/_layouts/default.html b/actionbarsherlock/website/resources/_layouts/default.html
index 4fd89c98d..d692eff0c 100644
--- a/actionbarsherlock/website/resources/_layouts/default.html
+++ b/actionbarsherlock/website/resources/_layouts/default.html
@@ -63,7 +63,7 @@
-
+
@@ -146,11 +146,11 @@
-
+
@@ -158,7 +158,7 @@