From 96675b1df3969f2d313b68f60ed9fa36805db8ce Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Thu, 10 Jun 2010 18:58:59 -0700 Subject: [PATCH] Merging ActionBar menu with options menu. Options menu items may now specify if they would like to appear in the action bar. Menu items defined in xml may set the showAsAction attribute to one of "never"(default), "ifRoom", or "always". Action buttons are populated as follows: * All showAsAction="always" items become action buttons, even if it would crowd the navigation area of the action bar. * If there is space remaining, showAsAction="ifRoom" items are added until no more will fit comfortably. Action button click events are now handled by the onOptionsItemSelected method used by the standard options menu. The construction of options menus now happens earlier in order to provide data to the action bar. Activities with an action bar can now expect to have onCreateOptionsMenu called when activity start-up is complete. Activity#invalidateOptionsMenu can be used to force a refresh of menu items where the previous API would use ActionBar#updateActionMenu. Change-Id: If52ddf1cf9f6926206bcdeadf42072ea2c24fab9 --- api/current.xml | 183 ++++++------- core/java/android/app/ActionBar.java | 63 ----- core/java/android/app/Activity.java | 4 + core/java/android/view/ActionBarView.java | 253 +++--------------- core/java/android/view/MenuInflater.java | 17 +- .../android/internal/app/SplitActionBar.java | 9 - .../view/menu/ActionMenuItemView.java | 112 ++++++++ .../internal/view/menu/ActionMenuView.java | 74 +++++ .../internal/view/menu/IconMenuView.java | 2 +- .../internal/view/menu/MenuBuilder.java | 100 ++++++- .../internal/view/menu/MenuItemImpl.java | 40 +++ .../res/layout/action_menu_item_layout.xml | 20 ++ core/res/res/layout/action_menu_layout.xml | 20 ++ core/res/res/values/attrs.xml | 13 +- core/res/res/values/public.xml | 5 + core/res/res/values/styles.xml | 3 + core/res/res/values/themes.xml | 2 + .../internal/policy/impl/PhoneWindow.java | 33 +-- 18 files changed, 543 insertions(+), 410 deletions(-) create mode 100644 core/java/com/android/internal/view/menu/ActionMenuItemView.java create mode 100644 core/java/com/android/internal/view/menu/ActionMenuView.java create mode 100644 core/res/res/layout/action_menu_item_layout.xml create mode 100644 core/res/res/layout/action_menu_layout.xml diff --git a/api/current.xml b/api/current.xml index 8a6b09a6dda4f..5ad31db516fe3 100644 --- a/api/current.xml +++ b/api/current.xml @@ -2088,6 +2088,28 @@ visibility="public" > + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + menu. - * - *

This is only called once, the first time the action bar is displayed. - * - * @param menu The action menu in which to place your items. - * @return You must return true for actions to be displayed; - * if you return false they will not be shown. - * - * @see #onActionItemClicked(MenuItem) - */ - public boolean onCreateActionMenu(Menu menu); - - /** - * Update the action bar. This is called in response to {@link #updateActionMenu()} - * calls, which may be application-initiated or the result of changing fragment state. - * - * @return true if the action bar should update based on altered menu contents, - * false if no changes are necessary. - */ - public boolean onUpdateActionMenu(Menu menu); - - /** - * This hook is called whenever an action item in your action bar is clicked. - * The default implementation simply returns false to have the normal - * processing happen (sending a message to its handler). You can use this - * method for any items for which you would like to do processing without - * those other facilities. - * - * @param item The action bar item that was selected. - * @return boolean Return false to allow normal menu processing to proceed, - * true to consume it here. - */ - public boolean onActionItemClicked(MenuItem item); - /** * This method is called whenever a navigation item in your action bar * is selected. @@ -276,18 +225,6 @@ public abstract class ActionBar { * Extend this if you only need a subset of Callback functionality. */ public static class SimpleCallback implements Callback { - public boolean onCreateActionMenu(Menu menu) { - return false; - } - - public boolean onUpdateActionMenu(Menu menu) { - return false; - } - - public boolean onActionItemClicked(MenuItem item) { - return false; - } - public boolean onCreateContextMode(int modeId, Menu menu) { return false; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ed37058520579..382f1bd8a8f84 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -936,6 +936,10 @@ public class Activity extends ContextThemeWrapper mTitleReady = true; onTitleChanged(getTitle(), getTitleColor()); } + if (mWindow != null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { + // Invalidate the action bar menu so that it can initialize properly. + mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); + } mCalled = true; } diff --git a/core/java/android/view/ActionBarView.java b/core/java/android/view/ActionBarView.java index 3ed345e4aa242..39668474f985d 100644 --- a/core/java/android/view/ActionBarView.java +++ b/core/java/android/view/ActionBarView.java @@ -16,7 +16,10 @@ package android.view; -import java.util.ArrayList; +import com.android.internal.R; +import com.android.internal.view.menu.ActionMenu; +import com.android.internal.view.menu.ActionMenuItem; +import com.android.internal.view.menu.MenuBuilder; import android.app.ActionBar; import android.app.Activity; @@ -37,10 +40,6 @@ import android.widget.Spinner; import android.widget.SpinnerAdapter; import android.widget.TextView; -import com.android.internal.R; -import com.android.internal.view.menu.ActionMenu; -import com.android.internal.view.menu.ActionMenuItem; - /** * @hide */ @@ -75,7 +74,6 @@ public class ActionBarView extends ViewGroup { private CharSequence mSubtitle; private Drawable mIcon; private Drawable mLogo; - private Drawable mDivider; private ImageView mIconView; private ImageView mLogoView; @@ -86,25 +84,14 @@ public class ActionBarView extends ViewGroup { private boolean mShowMenu; + private MenuBuilder mOptionsMenu; + private View mMenuView; + private ActionMenuItem mLogoNavItem; - private ActionMenu mActionMenu; - private ActionMenu mOptionsMenu; private SparseArray mContextMenus; private Callback mCallback; - - private final ArrayList mActions = new ArrayList(); - private final OnClickListener mActionClickHandler = new OnClickListener() { - public void onClick(View v) { - ActionView av = (ActionView) v; - ActionMenuItem item = (ActionMenuItem) av.menuItem; - - if (mCallback == null || !mCallback.onActionItemClicked(item)) { - item.invoke(); - } - } - }; private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = new AdapterView.OnItemSelectedListener() { @@ -151,7 +138,6 @@ public class ActionBarView extends ViewGroup { if (mIcon == null) { mIcon = info.loadIcon(pm); } - mDivider = a.getDrawable(R.styleable.ActionBar_divider); Drawable background = a.getDrawable(R.styleable.ActionBar_background); if (background != null) { @@ -178,8 +164,10 @@ public class ActionBarView extends ViewGroup { mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle); mHomeClickListener = new OnClickListener() { public void onClick(View v) { - if (mCallback != null) { - mCallback.onActionItemClicked(mLogoNavItem); + Context context = getContext(); + if (context instanceof Activity) { + Activity activity = (Activity) context; + activity.onOptionsItemSelected(mLogoNavItem); } } }; @@ -188,31 +176,22 @@ public class ActionBarView extends ViewGroup { mContextMenus = new SparseArray(); } - private boolean initOptionsMenu() { - final Context context = getContext(); - if (!(context instanceof Activity)) { - return false; - } - - final Activity activity = (Activity) context; - ActionMenu optionsMenu = new ActionMenu(context); - if (activity.onCreateOptionsMenu(optionsMenu)) { - mOptionsMenu = optionsMenu; - return true; - } - - return false; + public void setCallback(Callback callback) { + mCallback = callback; } - public void setCallback(Callback callback) { - final Context context = getContext(); - mCallback = callback; - - ActionMenu actionMenu = new ActionMenu(context); - if (callback.onCreateActionMenu(actionMenu)) { - mActionMenu = actionMenu; - performUpdateActionMenu(); + public void setMenu(Menu menu) { + MenuBuilder builder = (MenuBuilder) menu; + mOptionsMenu = builder; + if (mMenuView != null) { + removeView(mMenuView); } + final View menuView = builder.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, null); + final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT); + menuView.setLayoutParams(layoutParams); + addView(menuView); + mMenuView = menuView; } public void setCustomNavigationView(View view) { @@ -222,10 +201,6 @@ public class ActionBarView extends ViewGroup { } } - public void setDividerDrawable(Drawable d) { - mDivider = d; - } - public CharSequence getTitle() { return mTitle; } @@ -301,9 +276,13 @@ public class ActionBarView extends ViewGroup { mSpinner = new Spinner(mContext, null, com.android.internal.R.attr.dropDownSpinnerStyle); mSpinner.setOnItemSelectedListener(mNavItemSelectedListener); + mSpinner.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT)); addView(mSpinner); break; case ActionBar.NAVIGATION_MODE_CUSTOM: + mCustomNavView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT)); addView(mCustomNavView); break; } @@ -328,18 +307,6 @@ public class ActionBarView extends ViewGroup { return mDisplayOptions; } - private ActionView findActionViewForItem(MenuItem item) { - final ArrayList actions = mActions; - final int actionCount = actions.size(); - for (int i = 0; i < actionCount; i++) { - ActionView av = actions.get(i); - if (av.menuItem.equals(item)) { - return av; - } - } - return null; - } - public void setContextMode(int mode) { Callback callback = mCallback; if (callback == null) { @@ -368,92 +335,6 @@ public class ActionBarView extends ViewGroup { // TODO Turn off context mode; go back to normal. } - public void updateActionMenu() { - final ActionMenu menu = mActionMenu; - if (menu == null || mCallback == null || !mCallback.onUpdateActionMenu(menu)) { - return; - } - performUpdateActionMenu(); - } - - private void performUpdateActionMenu() { - final ActionMenu menu = mActionMenu; - if (menu == null) { - return; - } - final Context context = getContext(); - - int childCount = getChildCount(); - int childIndex = 0; - while (childIndex < childCount) { - View v = getChildAt(childIndex); - if (v instanceof ActionView) { - detachViewFromParent(childIndex); - childCount--; - } else { - childIndex++; - } - } - - ArrayList detachedViews = new ArrayList(mActions); - final int itemCount = menu.size(); - for (int i = 0; i < itemCount; i++) { - final MenuItem item = menu.getItem(i); - - boolean newView = false; - ActionView actionView = findActionViewForItem(item); - if (actionView == null) { - actionView = new ActionView(context); - newView = true; - } - actionView.actionId = item.getItemId(); - actionView.menuItem = item; - actionView.actionLabel = item.getTitle(); - actionView.setAdjustViewBounds(true); - actionView.setImageDrawable(item.getIcon()); - actionView.setFocusable(true); - actionView.setOnClickListener(mActionClickHandler); - - LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT, LayoutParams.ITEM_TYPE_ACTION); - actionView.setLayoutParams(layoutParams); - - if (newView) { - addView(actionView); - mActions.add(actionView); - } else { - attachViewToParent(actionView, -1, layoutParams); - detachedViews.remove(actionView); - actionView.invalidate(); - } - } - - final int detachedCount = detachedViews.size(); - for (int i = 0; i < detachedCount; i++) { - removeDetachedView(detachedViews.get(i), false); - } - - requestLayout(); - } - - public void addAction(int id, Drawable icon, CharSequence label, OnActionListener listener) { - ActionView actionView = new ActionView(getContext()); - actionView.actionId = id; - actionView.actionLabel = label; - actionView.actionListener = listener; - actionView.setAdjustViewBounds(true); - actionView.setImageDrawable(icon); - actionView.setOnClickListener(mActionClickHandler); - - actionView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT, LayoutParams.ITEM_TYPE_ACTION)); - - addView(actionView); - mActions.add(actionView); - - requestLayout(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -463,7 +344,7 @@ public class ActionBarView extends ViewGroup { mLogoView = new ImageView(getContext()); mLogoView.setAdjustViewBounds(true); mLogoView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT, LayoutParams.ITEM_TYPE_ICON)); + LayoutParams.MATCH_PARENT)); mLogoView.setImageDrawable(mLogo); mLogoView.setClickable(true); mLogoView.setFocusable(true); @@ -473,7 +354,7 @@ public class ActionBarView extends ViewGroup { mIconView = new ImageView(getContext()); mIconView.setAdjustViewBounds(true); mIconView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.MATCH_PARENT, LayoutParams.ITEM_TYPE_ICON)); + LayoutParams.MATCH_PARENT)); mIconView.setImageDrawable(mIcon); mIconView.setClickable(true); mIconView.setFocusable(true); @@ -508,8 +389,8 @@ public class ActionBarView extends ViewGroup { private void initTitle() { LayoutInflater inflater = LayoutInflater.from(getContext()); mTitleView = (TextView) inflater.inflate(R.layout.action_bar_title_item, null); - mTitleView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT, LayoutParams.ITEM_TYPE_TITLE)); + mTitleView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT)); if (mTitle != null) { mTitleView.setText(mTitle); } @@ -542,12 +423,9 @@ public class ActionBarView extends ViewGroup { if (mIconView != null && mIconView.getVisibility() != GONE) { availableWidth = measureChildView(mIconView, availableWidth, childSpecHeight, mSpacing); } - - final ArrayList actions = mActions; - final int actionCount = actions.size(); - for (int i = 0; i < actionCount; i++) { - ActionView action = actions.get(i); - availableWidth = measureChildView(action, availableWidth, + + if (mMenuView != null) { + availableWidth = measureChildView(mMenuView, availableWidth, childSpecHeight, mActionSpacing); } @@ -619,10 +497,9 @@ public class ActionBarView extends ViewGroup { x = r - l - getPaddingRight(); - final int count = mActions.size(); - for (int i = count - 1; i >= 0; i--) { - ActionView action = mActions.get(i); - x -= (positionChildInverse(action, x, y, contentHeight) + mActionSpacing); + + if (mMenuView != null) { + x -= positionChildInverse(mMenuView, x, y, contentHeight) + mActionSpacing; } } @@ -645,58 +522,4 @@ public class ActionBarView extends ViewGroup { return childWidth; } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new ViewGroup.LayoutParams(getContext(), attrs); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p != null && p instanceof LayoutParams; - } - - private static class LayoutParams extends ViewGroup.LayoutParams { - static final int ITEM_TYPE_UNKNOWN = -1; - static final int ITEM_TYPE_ICON = 0; - static final int ITEM_TYPE_TITLE = 1; - static final int ITEM_TYPE_CUSTOM_NAV = 2; - static final int ITEM_TYPE_ACTION = 3; - static final int ITEM_TYPE_MORE = 4; - - int type = ITEM_TYPE_UNKNOWN; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, int type) { - this(width, height); - this.type = type; - } - - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - } - - public interface OnActionListener { - void onAction(int id); - } - - private static class ActionView extends ImageView { - int actionId; - CharSequence actionLabel; - OnActionListener actionListener; - MenuItem menuItem; - - public ActionView(Context context) { - super(context); - } - } } diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index a37d83dd36dbe..3335e85436c8d 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -241,6 +241,14 @@ public class MenuInflater { private boolean itemVisible; private boolean itemEnabled; + /** + * Sync to attrs.xml enum: + * - 0: never + * - 1: ifRoom + * - 2: always + */ + private int itemShowAsAction; + private String itemListenerMethodName; private static final int defaultGroupId = NO_ID; @@ -314,6 +322,7 @@ public class MenuInflater { itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked); itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible); itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled); + itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, 0); itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick); a.recycle(); @@ -344,8 +353,12 @@ public class MenuInflater { new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName)); } - if (itemCheckable >= 2 && item instanceof MenuItemImpl) { - ((MenuItemImpl) item).setExclusiveCheckable(true); + if (item instanceof MenuItemImpl) { + MenuItemImpl impl = (MenuItemImpl) item; + if (itemCheckable >= 2) { + impl.setExclusiveCheckable(true); + } + impl.setShowAsAction(itemShowAsAction); } } diff --git a/core/java/com/android/internal/app/SplitActionBar.java b/core/java/com/android/internal/app/SplitActionBar.java index 81294922007f0..05ae793959305 100644 --- a/core/java/com/android/internal/app/SplitActionBar.java +++ b/core/java/com/android/internal/app/SplitActionBar.java @@ -83,10 +83,6 @@ public class SplitActionBar extends ActionBar { mActionView.setBackgroundDrawable(d); } - public void setDividerDrawable(Drawable d) { - mActionView.setDividerDrawable(d); - } - public View getCustomNavigationView() { return mActionView.getCustomNavigationView(); } @@ -106,9 +102,4 @@ public class SplitActionBar extends ActionBar { public int getDisplayOptions() { return mActionView.getDisplayOptions(); } - - public void updateActionMenu() { - mActionView.updateActionMenu(); - } - } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java new file mode 100644 index 0000000000000..f0d9f6065e8d9 --- /dev/null +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -0,0 +1,112 @@ +/* + * 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.android.internal.view.menu; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.SoundEffectConstants; +import android.view.View; +import android.widget.ImageButton; + +/** + * @hide + */ +public class ActionMenuItemView extends ImageButton implements MenuView.ItemView { + private static final String TAG = "ActionMenuItemView"; + + private MenuItemImpl mItemData; + private CharSequence mTitle; + private MenuBuilder.ItemInvoker mItemInvoker; + + public ActionMenuItemView(Context context) { + this(context, null); + } + + public ActionMenuItemView(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.actionButtonStyle); + } + + public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public MenuItemImpl getItemData() { + return mItemData; + } + + public void initialize(MenuItemImpl itemData, int menuType) { + mItemData = itemData; + + setClickable(true); + setFocusable(true); + setTitle(itemData.getTitle()); + setIcon(itemData.getIcon()); + setId(itemData.getItemId()); + + setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); + setEnabled(itemData.isEnabled()); + } + + @Override + public boolean performClick() { + // Let the view's listener have top priority + if (super.performClick()) { + return true; + } + + if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) { + playSoundEffect(SoundEffectConstants.CLICK); + return true; + } else { + return false; + } + } + + public void setItemInvoker(MenuBuilder.ItemInvoker invoker) { + mItemInvoker = invoker; + } + + public boolean prefersCondensedTitle() { + return false; + } + + public void setCheckable(boolean checkable) { + // TODO Support checkable action items + } + + public void setChecked(boolean checked) { + // TODO Support checkable action items + } + + public void setIcon(Drawable icon) { + setImageDrawable(icon); + } + + public void setShortcut(boolean showShortcut, char shortcutKey) { + // Action buttons don't show text for shortcut keys. + } + + public void setTitle(CharSequence title) { + mTitle = title; + } + + public boolean showsIcon() { + return true; + } + +} diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java new file mode 100644 index 0000000000000..596e3de6c65a6 --- /dev/null +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -0,0 +1,74 @@ +/* + * 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.android.internal.view.menu; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import java.util.ArrayList; + +/** + * @hide + */ +public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView { + private static final String TAG = "ActionMenuView"; + + // TODO: Make this a ViewConfiguration constant. + private static final int MAX_ACTION_ITEMS = 3; + + private MenuBuilder mMenu; + + public ActionMenuView(Context context) { + this(context, null); + } + + public ActionMenuView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public boolean invokeItem(MenuItemImpl item) { + return mMenu.performItemAction(item, 0); + } + + public int getWindowAnimations() { + return 0; + } + + public void initialize(MenuBuilder menu, int menuType) { + menu.setMaxActionItems(MAX_ACTION_ITEMS); + mMenu = menu; + updateChildren(true); + } + + public void updateChildren(boolean cleared) { + removeAllViews(); + + final ArrayList itemsToShow = mMenu.getActionItems(); + final int itemCount = itemsToShow.size(); + + for (int i = 0; i < itemCount; i++) { + final MenuItemImpl itemData = itemsToShow.get(i); + addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, + this)); + } + } + + private void addItemView(ActionMenuItemView view) { + view.setItemInvoker(this); + addView(view); + } +} diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java index beb57ba2340c7..bbf7c689e298a 100644 --- a/core/java/com/android/internal/view/menu/IconMenuView.java +++ b/core/java/com/android/internal/view/menu/IconMenuView.java @@ -337,7 +337,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi // This method does a clear refresh of children removeAllViews(); - final ArrayList itemsToShow = mMenu.getVisibleItems(); + final ArrayList itemsToShow = mMenu.getNonActionItems(); final int numItems = itemsToShow.size(); final int numItemsThatCanFit = mMaxItems; // Minimum of the num that can fit and the num that we have diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 228d5d09b0ab2..a9622128d6e94 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -27,16 +27,17 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; +import android.util.Log; import android.util.SparseArray; import android.view.ContextThemeWrapper; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; -import android.view.LayoutInflater; import android.view.ContextMenu.ContextMenuInfo; import android.widget.AdapterView; import android.widget.BaseAdapter; @@ -54,7 +55,7 @@ public class MenuBuilder implements Menu { private static final String LOGTAG = "MenuBuilder"; /** The number of different menu types */ - public static final int NUM_TYPES = 3; + public static final int NUM_TYPES = 4; /** The menu type that represents the icon menu view */ public static final int TYPE_ICON = 0; /** The menu type that represents the expanded menu view */ @@ -65,6 +66,11 @@ public class MenuBuilder implements Menu { * have an ItemView. */ public static final int TYPE_DIALOG = 2; + + /** + * The menu type that represents a button in the application's action bar. + */ + public static final int TYPE_ACTION_BUTTON = 3; private static final String VIEWS_TAG = "android:views"; @@ -73,6 +79,7 @@ public class MenuBuilder implements Menu { com.android.internal.R.style.Theme_IconMenu, com.android.internal.R.style.Theme_ExpandedMenu, 0, + 0, }; // Order must be the same order as the TYPE_* @@ -80,6 +87,7 @@ public class MenuBuilder implements Menu { com.android.internal.R.layout.icon_menu_layout, com.android.internal.R.layout.expanded_menu_layout, 0, + com.android.internal.R.layout.action_menu_layout, }; // Order must be the same order as the TYPE_* @@ -87,6 +95,7 @@ public class MenuBuilder implements Menu { com.android.internal.R.layout.icon_menu_item_layout, com.android.internal.R.layout.list_menu_item_layout, com.android.internal.R.layout.list_menu_item_layout, + com.android.internal.R.layout.action_menu_item_layout, }; private static final int[] sCategoryToOrder = new int[] { @@ -130,6 +139,24 @@ public class MenuBuilder implements Menu { * fetched from {@link #getVisibleItems()} */ private boolean mIsVisibleItemsStale; + + /** + * 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; + /** + * The number of visible action buttons permitted in this menu + */ + private int mMaxActionItems; + /** + * Whether or not the items (or any one item's action state) has changed since it was + * last fetched. + */ + private boolean mIsActionItemsStale; /** * Current use case is Context Menus: As Views populate the context menu, each one has @@ -281,6 +308,10 @@ public class MenuBuilder implements Menu { mVisibleItems = new ArrayList(); mIsVisibleItemsStale = true; + mActionItems = new ArrayList(); + mNonActionItems = new ArrayList(); + mIsActionItemsStale = true; + mShortcutsVisible = (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS); } @@ -900,6 +931,7 @@ public class MenuBuilder implements Menu { private void onItemsChanged(boolean cleared) { if (!mPreventDispatchingItemsChanged) { if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true; + if (mIsActionItemsStale == false) mIsActionItemsStale = true; MenuType[] menuTypes = mMenuTypes; for (int i = 0; i < NUM_TYPES; i++) { @@ -920,6 +952,15 @@ public class MenuBuilder implements Menu { onItemsChanged(false); } + /** + * 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 + onItemsChanged(false); + } + ArrayList getVisibleItems() { if (!mIsVisibleItemsStale) return mVisibleItems; @@ -934,9 +975,64 @@ public class MenuBuilder implements Menu { } mIsVisibleItemsStale = false; + mIsActionItemsStale = true; return mVisibleItems; } + + private void flagActionItems() { + if (!mIsActionItemsStale) { + return; + } + + final ArrayList visibleItems = getVisibleItems(); + final int itemsSize = visibleItems.size(); + int maxActions = mMaxActionItems; + + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + if (item.requiresActionButton()) { + maxActions--; + } + } + + // Flag as many more requested items as will fit. + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + if (item.requestsActionButton()) { + item.setIsActionButton(maxActions > 0); + maxActions--; + } + } + + mActionItems.clear(); + mNonActionItems.clear(); + for (int i = 0; i < itemsSize; i++) { + MenuItemImpl item = visibleItems.get(i); + if (item.isActionButton()) { + mActionItems.add(item); + } else { + mNonActionItems.add(item); + } + } + + mIsActionItemsStale = false; + } + + ArrayList getActionItems() { + flagActionItems(); + return mActionItems; + } + + ArrayList getNonActionItems() { + flagActionItems(); + return mNonActionItems; + } + + void setMaxActionItems(int maxActionItems) { + mMaxActionItems = maxActionItems; + mIsActionItemsStale = true; + } public void clearHeader() { mHeaderIcon = null; diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 9b582057fa7cd..dc1e81f04b1dc 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -74,6 +74,15 @@ public final class MenuItemImpl implements MenuItem { 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; + + /* + * These should be kept in sync with attrs.xml enum constants for showAsAction + */ + private static final int SHOW_AS_ACTION_NEVER = 0; + private static final int SHOW_AS_ACTION_IF_ROOM = 1; + private static final int SHOW_AS_ACTION_ALWAYS = 2; + private int mShowAsAction = SHOW_AS_ACTION_NEVER; /** Used for the icon resource ID if this item does not have an icon */ static final int NO_ICON = 0; @@ -630,4 +639,35 @@ public final class MenuItemImpl implements MenuItem { public boolean shouldShowIcon(int menuType) { return menuType == MenuBuilder.TYPE_ICON || mMenu.getOptionalIconsVisible(); } + + public boolean isActionButton() { + return (mFlags & IS_ACTION) == IS_ACTION || requiresActionButton(); + } + + public boolean requestsActionButton() { + return mShowAsAction == SHOW_AS_ACTION_IF_ROOM; + } + + public boolean requiresActionButton() { + return mShowAsAction == SHOW_AS_ACTION_ALWAYS; + } + + public void setIsActionButton(boolean isActionButton) { + if (isActionButton) { + mFlags |= IS_ACTION; + } else { + mFlags &= ~IS_ACTION; + } + } + + /** + * Used by MenuInflater to set how this item should display in the presence of + * an Action Bar. + * + * @param actionEnum Enum flag as defined in attrs.xml/SHOW_AS_ constants. + */ + public void setShowAsAction(int actionEnum) { + mShowAsAction = actionEnum; + mMenu.onItemActionRequestChanged(this); + } } diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml new file mode 100644 index 0000000000000..3f06251e79015 --- /dev/null +++ b/core/res/res/layout/action_menu_item_layout.xml @@ -0,0 +1,20 @@ + + + + diff --git a/core/res/res/layout/action_menu_layout.xml b/core/res/res/layout/action_menu_layout.xml new file mode 100644 index 0000000000000..18d5531a81d5e --- /dev/null +++ b/core/res/res/layout/action_menu_layout.xml @@ -0,0 +1,20 @@ + + + + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d3e66ee1150a5..d984575079840 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -397,8 +397,12 @@ - + + + + + @@ -3287,6 +3291,13 @@ called when the item is clicked. --> + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 8b1c9d46f6699..3dba1a07c5a40 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1294,10 +1294,15 @@ + + + + + diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 4f74413f40f3c..5ed26d3e2b440 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -873,4 +873,7 @@ useLogo @android:drawable/action_bar_divider + + diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bc5f610076d33..1f9f1367d2b41 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -170,6 +170,8 @@ @android:style/Widget.HorizontalScrollView @android:style/Widget.Spinner @android:style/Widget.Spinner.DropDown + @android:style/Widget.Spinner.DropDown + @android:style/Widget.ActionButton @android:style/Widget.CompoundButton.Star @android:style/Widget.TabWidget @android:style/Widget.TextView diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 1c78a01d1c79a..eea855d6f1799 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -44,7 +44,6 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -63,7 +62,6 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; @@ -81,12 +79,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.internal.view.menu.ContextMenuBuilder; -import com.android.internal.view.menu.MenuBuilder; -import com.android.internal.view.menu.MenuDialogHelper; -import com.android.internal.view.menu.MenuView; -import com.android.internal.view.menu.SubMenuBuilder; - /** * Android-specific Window. *

@@ -307,7 +299,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Already prepared (isPrepared will be reset to false later) if (st.isPrepared) return true; - + if ((mPreparedPanel != null) && (mPreparedPanel != st)) { // Another Panel is prepared and possibly open, so close it closePanel(mPreparedPanel, false); @@ -334,7 +326,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return false; } + st.refreshMenuContent = false; + + if (mActionBar != null) { + mActionBar.setMenu(st.menu); + } } // Callback and return if the callback does not want to show the menu @@ -383,11 +380,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { clearMenuViews(st); } } - } private static void clearMenuViews(PanelFeatureState st) { - // This can be called on config changes, so we should make sure // the views will be reconstructed based on the new orientation, etc. @@ -562,6 +557,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } st.refreshMenuContent = true; st.refreshDecorView = true; + + // Prepare the options panel if we have an action bar + if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) + && mActionBar != null) { + st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); + if (st != null) { + st.isPrepared = false; + preparePanel(st, null); + } + } } /** @@ -2295,10 +2300,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } else { mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); - if (mActionBar != null) { - if (mActionBar.getTitle() == null) { - mActionBar.setTitle(mTitle); - } + if (mActionBar != null && mActionBar.getTitle() == null) { + mActionBar.setTitle(mTitle); } } }