pt 1 in reviving the android build (copied from pm 28a1, wish me luck)

This commit is contained in:
wuggy 2026-04-08 15:43:25 -07:00
commit d7788a6d6d
4249 changed files with 468189 additions and 0 deletions

View file

@ -0,0 +1,928 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
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.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GeckoMenu extends ListView
implements Menu,
AdapterView.OnItemClickListener,
GeckoMenuItem.OnShowAsActionChangedListener {
private static final String LOGTAG = "GeckoMenu";
/**
* Controls whether off-UI-thread method calls in this class cause an
* exception or just logging.
*/
private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_OR_BETA ? AssertBehavior.NONE : AssertBehavior.THROW;
/*
* A callback for a menu item click/long click event.
*/
public static interface Callback {
// Called when a menu item is clicked, with the actual menu item as the argument.
public boolean onMenuItemClick(MenuItem item);
// Called when a menu item is long-clicked, with the actual menu item as the argument.
public boolean onMenuItemLongClick(MenuItem item);
}
/*
* An interface for a presenter to show the menu.
* Either an Activity or a View can be a presenter, that can watch for events
* and show/hide menu.
*/
public static interface MenuPresenter {
// Open the menu.
public void openMenu();
// Show the actual view containing the menu items. This can either be a parent or sub-menu.
public void showMenu(View menu);
// Close the menu.
public void closeMenu();
}
/*
* An interface for a presenter of action-items.
* Either an Activity or a View can be a presenter, that can watch for events
* and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
* DefaultActionItemBar, that shows the action-items as a header over list-view.
*/
public static interface ActionItemBarPresenter {
// Add an action-item.
public boolean addActionItem(View actionItem);
// Remove an action-item.
public void removeActionItem(View actionItem);
}
protected static final int NO_ID = 0;
// List of all menu items.
private final List<GeckoMenuItem> mItems;
// Quick lookup array used to make a fast path in findItem.
private final SparseArray<MenuItem> mItemsById;
// Map of "always" action-items in action-bar and their views.
private final Map<GeckoMenuItem, View> mPrimaryActionItems;
// Map of "ifRoom" action-items in action-bar and their views.
private final Map<GeckoMenuItem, View> mSecondaryActionItems;
// Map of "collapseActionView" action-items in action-bar and their views.
private final Map<GeckoMenuItem, View> mQuickShareActionItems;
// Reference to a callback for menu events.
private Callback mCallback;
// Reference to menu presenter.
private MenuPresenter mMenuPresenter;
// Reference to "always" action-items bar in action-bar.
private ActionItemBarPresenter mPrimaryActionItemBar;
// Reference to "ifRoom" action-items bar in action-bar.
private final ActionItemBarPresenter mSecondaryActionItemBar;
// Reference to "collapseActionView" action-items bar in action-bar.
private final ActionItemBarPresenter mQuickShareActionItemBar;
// Adapter to hold the list of menu items.
private final MenuItemsAdapter mAdapter;
// Show/hide icons in the list.
boolean mShowIcons;
public GeckoMenu(Context context) {
this(context, null);
}
public GeckoMenu(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.geckoMenuListViewStyle);
}
public GeckoMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
// Attach an adapter.
mAdapter = new MenuItemsAdapter();
setAdapter(mAdapter);
setOnItemClickListener(this);
mItems = new ArrayList<GeckoMenuItem>();
mItemsById = new SparseArray<MenuItem>();
mPrimaryActionItems = new HashMap<GeckoMenuItem, View>();
mSecondaryActionItems = new HashMap<GeckoMenuItem, View>();
mQuickShareActionItems = new HashMap<GeckoMenuItem, View>();
mPrimaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
mSecondaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
mQuickShareActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
}
private static void assertOnUiThread() {
ThreadUtils.assertOnUiThread(THREAD_ASSERT_BEHAVIOR);
}
@Override
public MenuItem add(CharSequence title) {
GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
addItem(menuItem);
return menuItem;
}
@Override
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, titleRes);
addItem(menuItem);
return menuItem;
}
@Override
public MenuItem add(int titleRes) {
GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, titleRes);
addItem(menuItem);
return menuItem;
}
@Override
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, title);
addItem(menuItem);
return menuItem;
}
private void addItem(GeckoMenuItem menuItem) {
assertOnUiThread();
menuItem.setOnShowAsActionChangedListener(this);
mAdapter.addMenuItem(menuItem);
mItems.add(menuItem);
}
private boolean addActionItem(final GeckoMenuItem menuItem) {
assertOnUiThread();
menuItem.setOnShowAsActionChangedListener(this);
final View actionView = menuItem.getActionView();
final int actionEnum = menuItem.getActionEnum();
boolean added = false;
if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
if (mPrimaryActionItems.size() == 0 &&
mPrimaryActionItemBar instanceof DefaultActionItemBar) {
// Reset the adapter before adding the header view to a list.
setAdapter(null);
addHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
setAdapter(mAdapter);
}
if (added = mPrimaryActionItemBar.addActionItem(actionView)) {
mPrimaryActionItems.put(menuItem, actionView);
mItems.add(menuItem);
}
} else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
if (mSecondaryActionItems.size() == 0) {
// Reset the adapter before adding the header view to a list.
setAdapter(null);
addHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
setAdapter(mAdapter);
}
if (added = mSecondaryActionItemBar.addActionItem(actionView)) {
mSecondaryActionItems.put(menuItem, actionView);
mItems.add(menuItem);
}
} else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) {
if (actionView instanceof MenuItemSwitcherLayout) {
final MenuItemSwitcherLayout quickShareView = (MenuItemSwitcherLayout) actionView;
// We don't want to add the quick share bar if we don't have any quick share items.
if (quickShareView.getActionButtonCount() > 0 &&
(added = mQuickShareActionItemBar.addActionItem(quickShareView))) {
if (mQuickShareActionItems.size() == 0) {
// Reset the adapter before adding the header view to a list.
setAdapter(null);
addHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
setAdapter(mAdapter);
}
mQuickShareActionItems.put(menuItem, quickShareView);
mItems.add(menuItem);
}
}
}
// Set the listeners.
if (actionView instanceof MenuItemActionBar) {
((MenuItemActionBar) actionView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handleMenuItemClick(menuItem);
}
});
((MenuItemActionBar) actionView).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (handleMenuItemLongClick(menuItem)) {
GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
return true;
}
return false;
}
});
} else if (actionView instanceof MenuItemSwitcherLayout) {
((MenuItemSwitcherLayout) actionView).setMenuItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handleMenuItemClick(menuItem);
}
});
((MenuItemSwitcherLayout) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (handleMenuItemLongClick(menuItem)) {
GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
return true;
}
return false;
}
});
}
return added;
}
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
return 0;
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
MenuItem menuItem = add(groupId, itemId, order, title);
return addSubMenu(menuItem);
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
MenuItem menuItem = add(groupId, itemId, order, titleRes);
return addSubMenu(menuItem);
}
@Override
public SubMenu addSubMenu(CharSequence title) {
MenuItem menuItem = add(title);
return addSubMenu(menuItem);
}
@Override
public SubMenu addSubMenu(int titleRes) {
MenuItem menuItem = add(titleRes);
return addSubMenu(menuItem);
}
private SubMenu addSubMenu(MenuItem menuItem) {
GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
subMenu.setMenuItem(menuItem);
subMenu.setCallback(mCallback);
subMenu.setMenuPresenter(mMenuPresenter);
((GeckoMenuItem) menuItem).setSubMenu(subMenu);
return subMenu;
}
private void removePrimaryActionBarView() {
// Reset the adapter before removing the header view from a list.
setAdapter(null);
removeHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
setAdapter(mAdapter);
}
private void removeSecondaryActionBarView() {
// Reset the adapter before removing the header view from a list.
setAdapter(null);
removeHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
setAdapter(mAdapter);
}
private void removeQuickShareActionBarView() {
// Reset the adapter before removing the header view from a list.
setAdapter(null);
removeHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
setAdapter(mAdapter);
}
@Override
public void clear() {
assertOnUiThread();
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
SubMenu sub = menuItem.getSubMenu();
if (sub == null) {
continue;
}
try {
sub.clear();
} catch (Exception ex) {
Log.e(LOGTAG, "Couldn't clear submenu.", ex);
}
}
}
mAdapter.clear();
mItems.clear();
/*
* Reinflating the menu will re-add any action items to the toolbar, so
* remove the old ones. This also ensures that any text associated with
* these is switched to the correct locale.
*/
if (mPrimaryActionItemBar != null) {
for (View item : mPrimaryActionItems.values()) {
mPrimaryActionItemBar.removeActionItem(item);
}
}
mPrimaryActionItems.clear();
if (mSecondaryActionItemBar != null) {
for (View item : mSecondaryActionItems.values()) {
mSecondaryActionItemBar.removeActionItem(item);
}
}
mSecondaryActionItems.clear();
if (mQuickShareActionItemBar != null) {
for (View item : mQuickShareActionItems.values()) {
mQuickShareActionItemBar.removeActionItem(item);
}
}
mQuickShareActionItems.clear();
// Remove the view, too -- the first addActionItem will re-add it,
// and this is simpler than changing that logic.
if (mPrimaryActionItemBar instanceof DefaultActionItemBar) {
removePrimaryActionBarView();
}
removeSecondaryActionBarView();
removeQuickShareActionBarView();
}
@Override
public void close() {
if (mMenuPresenter != null)
mMenuPresenter.closeMenu();
}
private void showMenu(View viewForMenu) {
if (mMenuPresenter != null)
mMenuPresenter.showMenu(viewForMenu);
}
@Override
public MenuItem findItem(int id) {
assertOnUiThread();
MenuItem quickItem = mItemsById.get(id);
if (quickItem != null) {
return quickItem;
}
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.getItemId() == id) {
mItemsById.put(id, menuItem);
return menuItem;
} else if (menuItem.hasSubMenu()) {
if (!menuItem.hasActionProvider()) {
SubMenu subMenu = menuItem.getSubMenu();
MenuItem item = subMenu.findItem(id);
if (item != null) {
mItemsById.put(id, item);
return item;
}
}
}
}
return null;
}
@Override
public MenuItem getItem(int index) {
if (index < mItems.size())
return mItems.get(index);
return null;
}
@Override
public boolean hasVisibleItems() {
assertOnUiThread();
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.isVisible() &&
!mPrimaryActionItems.containsKey(menuItem) &&
!mSecondaryActionItems.containsKey(menuItem) &&
!mQuickShareActionItems.containsKey(menuItem))
return true;
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Close the menu if it is open and the hardware menu key is pressed.
if (keyCode == KeyEvent.KEYCODE_MENU && isShown()) {
close();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return true;
}
@Override
public boolean performIdentifierAction(int id, int flags) {
return false;
}
@Override
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
return false;
}
@Override
public void removeGroup(int groupId) {
}
@Override
public void removeItem(int id) {
assertOnUiThread();
GeckoMenuItem item = (GeckoMenuItem) findItem(id);
if (item == null)
return;
// Remove it from the cache.
mItemsById.remove(id);
// Remove it from any sub-menu.
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
SubMenu subMenu = menuItem.getSubMenu();
if (subMenu != null && subMenu.findItem(id) != null) {
subMenu.removeItem(id);
return;
}
}
}
// Remove it from own menu.
if (mPrimaryActionItems.containsKey(item)) {
if (mPrimaryActionItemBar != null)
mPrimaryActionItemBar.removeActionItem(mPrimaryActionItems.get(item));
mPrimaryActionItems.remove(item);
mItems.remove(item);
if (mPrimaryActionItems.size() == 0 &&
mPrimaryActionItemBar instanceof DefaultActionItemBar) {
removePrimaryActionBarView();
}
return;
}
if (mSecondaryActionItems.containsKey(item)) {
if (mSecondaryActionItemBar != null)
mSecondaryActionItemBar.removeActionItem(mSecondaryActionItems.get(item));
mSecondaryActionItems.remove(item);
mItems.remove(item);
if (mSecondaryActionItems.size() == 0) {
removeSecondaryActionBarView();
}
return;
}
if (mQuickShareActionItems.containsKey(item)) {
if (mQuickShareActionItemBar != null)
mQuickShareActionItemBar.removeActionItem(mQuickShareActionItems.get(item));
mQuickShareActionItems.remove(item);
mItems.remove(item);
if (mQuickShareActionItems.size() == 0) {
removeQuickShareActionBarView();
}
return;
}
mAdapter.removeMenuItem(item);
mItems.remove(item);
}
@Override
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
}
@Override
public void setGroupEnabled(int group, boolean enabled) {
}
@Override
public void setGroupVisible(int group, boolean visible) {
}
@Override
public void setQwertyMode(boolean isQwerty) {
}
@Override
public int size() {
return mItems.size();
}
@Override
public boolean hasActionItemBar() {
return (mPrimaryActionItemBar != null) &&
(mSecondaryActionItemBar != null) &&
(mQuickShareActionItemBar != null);
}
@Override
public void onShowAsActionChanged(GeckoMenuItem item) {
removeItem(item.getItemId());
if (item.isActionItem() && addActionItem(item)) {
return;
}
addItem(item);
}
public void onItemChanged(GeckoMenuItem item) {
assertOnUiThread();
if (item.isActionItem()) {
final View actionView;
final int actionEnum = item.getActionEnum();
if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
actionView = mPrimaryActionItems.get(item);
} else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
actionView = mSecondaryActionItems.get(item);
} else {
actionView = mQuickShareActionItems.get(item);
}
if (actionView != null) {
if (item.isVisible()) {
actionView.setVisibility(View.VISIBLE);
if (actionView instanceof MenuItemActionBar) {
((MenuItemActionBar) actionView).initialize(item);
} else {
((MenuItemSwitcherLayout) actionView).initialize(item);
}
} else {
actionView.setVisibility(View.GONE);
}
}
} else {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// We might be showing headers. Account them while using the position.
position -= getHeaderViewsCount();
GeckoMenuItem item = mAdapter.getItem(position);
handleMenuItemClick(item);
}
void handleMenuItemClick(GeckoMenuItem item) {
if (!item.isEnabled())
return;
if (item.invoke()) {
close();
} else if (item.hasSubMenu()) {
// Refresh the submenu for the provider.
GeckoActionProvider provider = item.getGeckoActionProvider();
if (provider != null) {
GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
subMenu.setShowIcons(true);
provider.onPrepareSubMenu(subMenu);
item.setSubMenu(subMenu);
}
// Show the submenu.
GeckoSubMenu subMenu = (GeckoSubMenu) item.getSubMenu();
showMenu(subMenu);
} else {
close();
mCallback.onMenuItemClick(item);
}
}
boolean handleMenuItemLongClick(GeckoMenuItem item) {
if (!item.isEnabled()) {
return false;
}
if (mCallback != null) {
if (mCallback.onMenuItemLongClick(item)) {
close();
return true;
}
}
return false;
}
public Callback getCallback() {
return mCallback;
}
public MenuPresenter getMenuPresenter() {
return mMenuPresenter;
}
public void setCallback(Callback callback) {
mCallback = callback;
// Update the submenus just in case this changes on the fly.
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
subMenu.setCallback(mCallback);
}
}
}
public void setMenuPresenter(MenuPresenter presenter) {
mMenuPresenter = presenter;
// Update the submenus just in case this changes on the fly.
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
subMenu.setMenuPresenter(mMenuPresenter);
}
}
}
public void setActionItemBarPresenter(ActionItemBarPresenter presenter) {
mPrimaryActionItemBar = presenter;
}
public void setShowIcons(boolean show) {
if (mShowIcons != show) {
mShowIcons = show;
mAdapter.notifyDataSetChanged();
}
}
// Action Items are added to the header view by default.
// URL bar can register itself as a presenter, in case it has a different place to show them.
public static class DefaultActionItemBar extends LinearLayout
implements ActionItemBarPresenter {
private final int mRowHeight;
private float mWeightSum;
public DefaultActionItemBar(Context context) {
this(context, null);
}
public DefaultActionItemBar(Context context, AttributeSet attrs) {
super(context, attrs);
mRowHeight = getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
}
@Override
public boolean addActionItem(View actionItem) {
ViewGroup.LayoutParams actualParams = actionItem.getLayoutParams();
LinearLayout.LayoutParams params;
if (actualParams != null) {
params = new LinearLayout.LayoutParams(actionItem.getLayoutParams());
params.width = 0;
} else {
params = new LinearLayout.LayoutParams(0, mRowHeight);
}
if (actionItem instanceof MenuItemSwitcherLayout) {
params.weight = ((MenuItemSwitcherLayout) actionItem).getChildCount();
} else {
params.weight = 1.0f;
}
mWeightSum += params.weight;
actionItem.setLayoutParams(params);
addView(actionItem);
setWeightSum(mWeightSum);
return true;
}
@Override
public void removeActionItem(View actionItem) {
if (indexOfChild(actionItem) != -1) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) actionItem.getLayoutParams();
mWeightSum -= params.weight;
removeView(actionItem);
}
}
}
// Adapter to bind menu items to the list.
private class MenuItemsAdapter extends BaseAdapter {
private static final int VIEW_TYPE_DEFAULT = 0;
private static final int VIEW_TYPE_ACTION_MODE = 1;
private final List<GeckoMenuItem> mItems;
public MenuItemsAdapter() {
mItems = new ArrayList<GeckoMenuItem>();
}
@Override
public int getCount() {
if (mItems == null)
return 0;
int visibleCount = 0;
for (GeckoMenuItem item : mItems) {
if (item.isVisible())
visibleCount++;
}
return visibleCount;
}
@Override
public GeckoMenuItem getItem(int position) {
for (GeckoMenuItem item : mItems) {
if (item.isVisible()) {
position--;
if (position < 0)
return item;
}
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
GeckoMenuItem item = getItem(position);
GeckoMenuItem.Layout view = null;
// Try to re-use the view.
if (convertView == null && getItemViewType(position) == VIEW_TYPE_DEFAULT) {
view = new MenuItemDefault(parent.getContext(), null);
} else {
view = (GeckoMenuItem.Layout) convertView;
}
if (view == null || view instanceof MenuItemSwitcherLayout) {
// Always get from the menu item.
// This will ensure that the default activity is refreshed.
view = (MenuItemSwitcherLayout) item.getActionView();
// ListView will not perform an item click if the row has a focusable view in it.
// Hence, forward the click event on the menu item in the action-view to the ListView.
final View actionView = (View) view;
final int pos = position;
final long id = getItemId(position);
((MenuItemSwitcherLayout) view).setMenuItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GeckoMenu listView = GeckoMenu.this;
listView.performItemClick(actionView, pos + listView.getHeaderViewsCount(), id);
}
});
}
// Initialize the view.
view.setShowIcon(mShowIcons);
view.initialize(item);
return (View) view;
}
@Override
public int getItemViewType(int position) {
return getItem(position).getGeckoActionProvider() == null ? VIEW_TYPE_DEFAULT : VIEW_TYPE_ACTION_MODE;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean areAllItemsEnabled() {
// Setting this to true is a workaround to fix disappearing
// dividers in the menu (bug 963249).
return true;
}
@Override
public boolean isEnabled(int position) {
// Setting this to true is a workaround to fix disappearing
// dividers in the menu in L (bug 1050780).
return true;
}
public void addMenuItem(GeckoMenuItem menuItem) {
if (mItems.contains(menuItem))
return;
// Insert it in proper order.
int index = 0;
for (GeckoMenuItem item : mItems) {
if (item.getOrder() > menuItem.getOrder()) {
mItems.add(index, menuItem);
notifyDataSetChanged();
return;
} else {
index++;
}
}
// Add the menuItem at the end.
mItems.add(menuItem);
notifyDataSetChanged();
}
public void removeMenuItem(GeckoMenuItem menuItem) {
// Remove it from the list.
mItems.remove(menuItem);
notifyDataSetChanged();
}
public void clear() {
mItemsById.clear();
mItems.clear();
notifyDataSetChanged();
}
public GeckoMenuItem getMenuItem(int id) {
for (GeckoMenuItem item : mItems) {
if (item.getItemId() == id)
return item;
}
return null;
}
}
}

View file

@ -0,0 +1,163 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import java.io.IOException;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.InflateException;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
public class GeckoMenuInflater extends MenuInflater {
private static final String TAG_MENU = "menu";
private static final String TAG_ITEM = "item";
private static final int NO_ID = 0;
private final Context mContext;
// Private class to hold the parsed menu item.
private class ParsedItem {
public int id;
public int order;
public CharSequence title;
public int iconRes;
public boolean checkable;
public boolean checked;
public boolean visible;
public boolean enabled;
public int showAsAction;
public boolean hasSubMenu;
}
public GeckoMenuInflater(Context context) {
super(context);
mContext = context;
}
@Override
public void inflate(int menuRes, Menu menu) {
// This does not check for a well-formed XML.
XmlResourceParser parser = null;
try {
parser = mContext.getResources().getXml(menuRes);
AttributeSet attrs = Xml.asAttributeSet(parser);
parseMenu(parser, attrs, menu);
} catch (XmlPullParserException | IOException e) {
throw new InflateException("Error inflating menu XML", e);
} finally {
if (parser != null)
parser.close();
}
}
private void parseMenu(XmlResourceParser parser, AttributeSet attrs, Menu menu)
throws XmlPullParserException, IOException {
ParsedItem item = null;
String tag;
int eventType = parser.getEventType();
do {
tag = parser.getName();
switch (eventType) {
case XmlPullParser.START_TAG:
if (tag.equals(TAG_ITEM)) {
// Parse the menu item.
item = new ParsedItem();
parseItem(item, attrs);
} else if (tag.equals(TAG_MENU)) {
if (item != null) {
// Add the submenu item.
SubMenu subMenu = menu.addSubMenu(NO_ID, item.id, item.order, item.title);
item.hasSubMenu = true;
// Set the menu item in main menu.
MenuItem menuItem = subMenu.getItem();
setValues(item, menuItem);
// Start parsing the sub menu.
parseMenu(parser, attrs, subMenu);
}
}
break;
case XmlPullParser.END_TAG:
if (parser.getName().equals(TAG_ITEM)) {
if (!item.hasSubMenu) {
// Add the item.
MenuItem menuItem = menu.add(NO_ID, item.id, item.order, item.title);
setValues(item, menuItem);
}
} else if (tag.equals(TAG_MENU)) {
return;
}
break;
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
}
public void parseItem(ParsedItem item, AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MenuItem);
item.id = a.getResourceId(R.styleable.MenuItem_android_id, NO_ID);
item.order = a.getInt(R.styleable.MenuItem_android_orderInCategory, 0);
item.title = a.getText(R.styleable.MenuItem_android_title);
item.checkable = a.getBoolean(R.styleable.MenuItem_android_checkable, false);
item.checked = a.getBoolean(R.styleable.MenuItem_android_checked, false);
item.visible = a.getBoolean(R.styleable.MenuItem_android_visible, true);
item.enabled = a.getBoolean(R.styleable.MenuItem_android_enabled, true);
item.hasSubMenu = false;
item.iconRes = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
item.showAsAction = a.getInt(R.styleable.MenuItem_android_showAsAction, 0);
a.recycle();
}
public void setValues(ParsedItem item, MenuItem menuItem) {
// We are blocking any presenter updates during inflation.
GeckoMenuItem geckoItem = null;
if (menuItem instanceof GeckoMenuItem) {
geckoItem = (GeckoMenuItem) menuItem;
}
if (geckoItem != null) {
geckoItem.stopDispatchingChanges();
}
menuItem.setChecked(item.checked)
.setVisible(item.visible)
.setEnabled(item.enabled)
.setCheckable(item.checkable)
.setIcon(item.iconRes);
menuItem.setShowAsAction(item.showAsAction);
if (geckoItem != null) {
// We don't need to allow presenter updates during inflation,
// so we use the weak form of re-enabling changes.
geckoItem.resumeDispatchingChanges();
}
}
}

View file

@ -0,0 +1,472 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.ActionProvider;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
public class GeckoMenuItem implements MenuItem {
private static final int SHARE_BAR_HISTORY_SIZE = 2;
// These values mirror MenuItem values that are only available on API >= 11.
public static final int SHOW_AS_ACTION_NEVER = 0;
public static final int SHOW_AS_ACTION_IF_ROOM = 1;
public static final int SHOW_AS_ACTION_ALWAYS = 2;
public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
// A View that can show a MenuItem should be able to initialize from
// the properties of the MenuItem.
public static interface Layout {
public void initialize(GeckoMenuItem item);
public void setShowIcon(boolean show);
}
public static interface OnShowAsActionChangedListener {
public boolean hasActionItemBar();
public void onShowAsActionChanged(GeckoMenuItem item);
}
private final int mId;
private final int mOrder;
private View mActionView;
private int mActionEnum;
private CharSequence mTitle;
private CharSequence mTitleCondensed;
private boolean mCheckable;
private boolean mChecked;
private boolean mVisible = true;
private boolean mEnabled = true;
private Drawable mIcon;
private int mIconRes;
private GeckoActionProvider mActionProvider;
private GeckoSubMenu mSubMenu;
private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
final GeckoMenu mMenu;
OnShowAsActionChangedListener mShowAsActionChangedListener;
private volatile boolean mShouldDispatchChanges = true;
private volatile boolean mDidChange;
public GeckoMenuItem(GeckoMenu menu, int id, int order, int titleRes) {
mMenu = menu;
mId = id;
mOrder = order;
mTitle = mMenu.getResources().getString(titleRes);
}
public GeckoMenuItem(GeckoMenu menu, int id, int order, CharSequence title) {
mMenu = menu;
mId = id;
mOrder = order;
mTitle = title;
}
/**
* Stop dispatching item changed events to presenters until
* [start|resume]DispatchingItemsChanged() is called. Useful when
* many menu operations are going to be performed as a batch.
*/
public void stopDispatchingChanges() {
mDidChange = false;
mShouldDispatchChanges = false;
}
/**
* Resume dispatching item changed events to presenters. This method
* will NOT call onItemChanged if any menu operations were queued.
* Only future menu operations will call onItemChanged. Useful for
* sequelching presenter updates.
*/
public void resumeDispatchingChanges() {
mShouldDispatchChanges = true;
}
/**
* Start dispatching item changed events to presenters. This method
* will call onItemChanged if any menu operations were queued.
*/
public void startDispatchingChanges() {
if (mDidChange) {
mMenu.onItemChanged(this);
}
mShouldDispatchChanges = true;
}
@Override
public boolean collapseActionView() {
return false;
}
@Override
public boolean expandActionView() {
return false;
}
public boolean hasActionProvider() {
return (mActionProvider != null);
}
public int getActionEnum() {
return mActionEnum;
}
public GeckoActionProvider getGeckoActionProvider() {
return mActionProvider;
}
@Override
public ActionProvider getActionProvider() {
return null;
}
@Override
public View getActionView() {
if (mActionProvider != null) {
return mActionProvider.onCreateActionView(SHARE_BAR_HISTORY_SIZE,
GeckoActionProvider.ActionViewType.DEFAULT);
}
return mActionView;
}
@Override
public char getAlphabeticShortcut() {
return 0;
}
@Override
public int getGroupId() {
return 0;
}
@Override
public Drawable getIcon() {
if (mIcon == null) {
if (mIconRes != 0)
return ResourceDrawableUtils.getDrawable(mMenu.getContext(), mIconRes);
else
return null;
} else {
return mIcon;
}
}
@Override
public Intent getIntent() {
return null;
}
@Override
public int getItemId() {
return mId;
}
@Override
public ContextMenu.ContextMenuInfo getMenuInfo() {
return null;
}
@Override
public char getNumericShortcut() {
return 0;
}
@Override
public int getOrder() {
return mOrder;
}
@Override
public SubMenu getSubMenu() {
return mSubMenu;
}
@Override
public CharSequence getTitle() {
return mTitle;
}
@Override
public CharSequence getTitleCondensed() {
return mTitleCondensed;
}
@Override
public boolean hasSubMenu() {
if (mActionProvider != null)
return mActionProvider.hasSubMenu();
return (mSubMenu != null);
}
public boolean isActionItem() {
return (mActionEnum > 0);
}
@Override
public boolean isActionViewExpanded() {
return false;
}
@Override
public boolean isCheckable() {
return mCheckable;
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public boolean isEnabled() {
return mEnabled;
}
@Override
public boolean isVisible() {
return mVisible;
}
@Override
public MenuItem setActionProvider(ActionProvider actionProvider) {
return this;
}
public MenuItem setActionProvider(GeckoActionProvider actionProvider) {
mActionProvider = actionProvider;
if (mActionProvider != null) {
actionProvider.setOnTargetSelectedListener(new GeckoActionProvider.OnTargetSelectedListener() {
@Override
public void onTargetSelected() {
mMenu.close();
// Refresh the menu item to show the high frequency apps.
mShowAsActionChangedListener.onShowAsActionChanged(GeckoMenuItem.this);
}
});
}
mShowAsActionChangedListener.onShowAsActionChanged(this);
return this;
}
@Override
public MenuItem setActionView(int resId) {
return this;
}
@Override
public MenuItem setActionView(View view) {
return this;
}
@Override
public MenuItem setAlphabeticShortcut(char alphaChar) {
return this;
}
@Override
public MenuItem setCheckable(boolean checkable) {
if (mCheckable != checkable) {
mCheckable = checkable;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setIcon(Drawable icon) {
if (mIcon != icon) {
mIcon = icon;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setIcon(int iconRes) {
if (mIconRes != iconRes) {
mIconRes = iconRes;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setIntent(Intent intent) {
return this;
}
@Override
public MenuItem setNumericShortcut(char numericChar) {
return this;
}
@Override
public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
return this;
}
@Override
public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener) {
mMenuItemClickListener = menuItemClickListener;
return this;
}
@Override
public MenuItem setShortcut(char numericChar, char alphaChar) {
return this;
}
@Override
public void setShowAsAction(int actionEnum) {
setShowAsAction(actionEnum, 0);
}
public void setShowAsAction(int actionEnum, int style) {
if (mShowAsActionChangedListener == null)
return;
if (mActionEnum == actionEnum)
return;
if (actionEnum > 0) {
if (!mShowAsActionChangedListener.hasActionItemBar())
return;
if (!hasActionProvider()) {
// Change the type to just an icon
MenuItemActionBar actionView;
if (style != 0) {
actionView = new MenuItemActionBar(mMenu.getContext(), null, style);
} else {
if (actionEnum == SHOW_AS_ACTION_ALWAYS) {
actionView = new MenuItemActionBar(mMenu.getContext());
} else {
actionView = new MenuItemActionBar(mMenu.getContext(), null, R.attr.menuItemSecondaryActionBarStyle);
}
}
actionView.initialize(this);
mActionView = actionView;
}
mActionEnum = actionEnum;
}
mShowAsActionChangedListener.onShowAsActionChanged(this);
}
@Override
public MenuItem setShowAsActionFlags(int actionEnum) {
return this;
}
public MenuItem setSubMenu(GeckoSubMenu subMenu) {
mSubMenu = subMenu;
return this;
}
@Override
public MenuItem setTitle(CharSequence title) {
if (!TextUtils.equals(mTitle, title)) {
mTitle = title;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
@Override
public MenuItem setTitle(int title) {
CharSequence newTitle = mMenu.getResources().getString(title);
return setTitle(newTitle);
}
@Override
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
return this;
}
@Override
public MenuItem setVisible(boolean visible) {
// Action views are not normal menu items and visibility can get out
// of sync unless we dispatch whenever required.
if (isActionItem() || mVisible != visible) {
mVisible = visible;
if (mShouldDispatchChanges) {
mMenu.onItemChanged(this);
} else {
mDidChange = true;
}
}
return this;
}
public boolean invoke() {
if (mMenuItemClickListener != null)
return mMenuItemClickListener.onMenuItemClick(this);
else
return false;
}
public void setOnShowAsActionChangedListener(OnShowAsActionChangedListener listener) {
mShowAsActionChangedListener = listener;
}
}

View file

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
public class GeckoSubMenu extends GeckoMenu
implements SubMenu {
private static final String LOGTAG = "GeckoSubMenu";
// MenuItem associated with this submenu.
private MenuItem mMenuItem;
public GeckoSubMenu(Context context) {
super(context);
}
public GeckoSubMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GeckoSubMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void clearHeader() {
}
public SubMenu setMenuItem(MenuItem item) {
mMenuItem = item;
return this;
}
@Override
public MenuItem getItem() {
return mMenuItem;
}
@Override
public SubMenu setHeaderIcon(Drawable icon) {
return this;
}
@Override
public SubMenu setHeaderIcon(int iconRes) {
return this;
}
@Override
public SubMenu setHeaderTitle(CharSequence title) {
return this;
}
@Override
public SubMenu setHeaderTitle(int titleRes) {
return this;
}
@Override
public SubMenu setHeaderView(View view) {
return this;
}
@Override
public SubMenu setIcon(Drawable icon) {
return this;
}
@Override
public SubMenu setIcon(int iconRes) {
return this;
}
}

View file

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.widget.themed.ThemedImageButton;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
public class MenuItemActionBar extends ThemedImageButton
implements GeckoMenuItem.Layout {
private static final String LOGTAG = "GeckoMenuItemActionBar";
public MenuItemActionBar(Context context) {
this(context, null);
}
public MenuItemActionBar(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemActionBarStyle);
}
public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void initialize(GeckoMenuItem item) {
if (item == null)
return;
setIcon(item.getIcon());
setTitle(item.getTitle());
setEnabled(item.isEnabled());
setId(item.getItemId());
}
void setIcon(Drawable icon) {
if (icon == null) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
setImageDrawable(icon);
}
}
void setIcon(int icon) {
setIcon((icon == 0) ? null : ResourceDrawableUtils.getDrawable(getContext(), icon));
}
void setTitle(CharSequence title) {
// set accessibility contentDescription here
setContentDescription(title);
}
@Override
public void setShowIcon(boolean show) {
// Do nothing.
}
}

View file

@ -0,0 +1,152 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;
public class MenuItemDefault extends TextView
implements GeckoMenuItem.Layout {
private static final int[] STATE_MORE = new int[] { R.attr.state_more };
private static final int[] STATE_CHECKED = new int[] { android.R.attr.state_checkable, android.R.attr.state_checked };
private static final int[] STATE_UNCHECKED = new int[] { android.R.attr.state_checkable };
private Drawable mIcon;
private final Drawable mState;
private static Rect sIconBounds;
private boolean mCheckable;
private boolean mChecked;
private boolean mHasSubMenu;
private boolean mShowIcon;
public MenuItemDefault(Context context) {
this(context, null);
}
public MenuItemDefault(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemDefaultStyle);
}
public MenuItemDefault(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources res = getResources();
int width = res.getDimensionPixelSize(R.dimen.menu_item_row_width);
int height = res.getDimensionPixelSize(R.dimen.menu_item_row_height);
setMinimumWidth(width);
setMinimumHeight(height);
int stateIconSize = res.getDimensionPixelSize(R.dimen.menu_item_state_icon);
Rect stateIconBounds = new Rect(0, 0, stateIconSize, stateIconSize);
mState = res.getDrawable(R.drawable.menu_item_state).mutate();
mState.setBounds(stateIconBounds);
if (sIconBounds == null) {
int iconSize = res.getDimensionPixelSize(R.dimen.menu_item_icon);
sIconBounds = new Rect(0, 0, iconSize, iconSize);
}
setCompoundDrawables(mIcon, null, mState, null);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
if (mHasSubMenu)
mergeDrawableStates(drawableState, STATE_MORE);
else if (mCheckable && mChecked)
mergeDrawableStates(drawableState, STATE_CHECKED);
else if (mCheckable && !mChecked)
mergeDrawableStates(drawableState, STATE_UNCHECKED);
return drawableState;
}
@Override
public void initialize(GeckoMenuItem item) {
if (item == null)
return;
setTitle(item.getTitle());
setIcon(item.getIcon());
setEnabled(item.isEnabled());
setCheckable(item.isCheckable());
setChecked(item.isChecked());
setSubMenuIndicator(item.hasSubMenu());
}
private void refreshIcon() {
setCompoundDrawables(mShowIcon ? mIcon : null, null, mState, null);
}
void setIcon(Drawable icon) {
mIcon = icon;
if (mIcon != null) {
mIcon.setBounds(sIconBounds);
mIcon.setAlpha(isEnabled() ? 255 : 99);
}
refreshIcon();
}
void setIcon(int icon) {
setIcon((icon == 0) ? null : ResourceDrawableUtils.getDrawable(getContext(), icon));
}
void setTitle(CharSequence title) {
setText(title);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (mIcon != null)
mIcon.setAlpha(enabled ? 255 : 99);
if (mState != null)
mState.setAlpha(enabled ? 255 : 99);
}
private void setCheckable(boolean checkable) {
if (mCheckable != checkable) {
mCheckable = checkable;
refreshDrawableState();
}
}
private void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
@Override
public void setShowIcon(boolean show) {
if (mShowIcon != show) {
mShowIcon = show;
refreshIcon();
}
}
void setSubMenuIndicator(boolean hasSubMenu) {
if (mHasSubMenu != hasSubMenu) {
mHasSubMenu = hasSubMenu;
refreshDrawableState();
}
}
}

View file

@ -0,0 +1,188 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.R;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
/**
* This class is a container view for menu items that:
* * Shows text if there is enough space and there are
* no action buttons ({@link #mActionButtons}).
* * Shows an icon if there is not enough space for text,
* or there are action buttons.
*/
public class MenuItemSwitcherLayout extends LinearLayout
implements GeckoMenuItem.Layout,
View.OnClickListener {
private final MenuItemDefault mMenuItem;
private final MenuItemActionBar mMenuButton;
private final List<ImageButton> mActionButtons;
private final List<View.OnClickListener> mActionButtonListeners = new ArrayList<View.OnClickListener>();
public MenuItemSwitcherLayout(Context context) {
this(context, null);
}
public MenuItemSwitcherLayout(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemSwitcherLayoutStyle);
}
@TargetApi(14)
public MenuItemSwitcherLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.menu_item_switcher_layout, this);
mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
mMenuButton = (MenuItemActionBar) findViewById(R.id.menu_item_button);
mActionButtons = new ArrayList<ImageButton>();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = right - left;
final View parent = (View) getParent();
final int parentPadding = parent.getPaddingLeft() + parent.getPaddingRight();
final int horizontalSpaceAvailableInParent = parent.getMeasuredWidth() - parentPadding;
// Check if there is another View sharing horizontal
// space with this View in the parent.
if (width < horizontalSpaceAvailableInParent || mActionButtons.size() != 0) {
// Use the icon.
mMenuItem.setVisibility(View.GONE);
mMenuButton.setVisibility(View.VISIBLE);
} else {
// Use the button.
mMenuItem.setVisibility(View.VISIBLE);
mMenuButton.setVisibility(View.GONE);
}
super.onLayout(changed, left, top, right, bottom);
}
@Override
public void initialize(GeckoMenuItem item) {
if (item == null) {
return;
}
mMenuItem.initialize(item);
mMenuButton.initialize(item);
setEnabled(item.isEnabled());
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mMenuItem.setEnabled(enabled);
mMenuButton.setEnabled(enabled);
for (ImageButton button : mActionButtons) {
button.setEnabled(enabled);
button.setAlpha(enabled ? 255 : 99);
}
}
public void setMenuItemClickListener(View.OnClickListener listener) {
mMenuItem.setOnClickListener(listener);
mMenuButton.setOnClickListener(listener);
}
public void setMenuItemLongClickListener(View.OnLongClickListener listener) {
mMenuItem.setOnLongClickListener(listener);
mMenuButton.setOnLongClickListener(listener);
}
public void addActionButtonClickListener(View.OnClickListener listener) {
mActionButtonListeners.add(listener);
}
@Override
public void setShowIcon(boolean show) {
mMenuItem.setShowIcon(show);
}
public void setIcon(Drawable icon) {
mMenuItem.setIcon(icon);
mMenuButton.setIcon(icon);
}
public void setIcon(int icon) {
mMenuItem.setIcon(icon);
mMenuButton.setIcon(icon);
}
public void setTitle(CharSequence title) {
mMenuItem.setTitle(title);
mMenuButton.setContentDescription(title);
}
public void setSubMenuIndicator(boolean hasSubMenu) {
mMenuItem.setSubMenuIndicator(hasSubMenu);
}
public void addActionButton(Drawable drawable, CharSequence label) {
// If this is the first icon, retain the text.
// If not, make the menu item an icon.
final int count = mActionButtons.size();
mMenuItem.setVisibility(View.GONE);
mMenuButton.setVisibility(View.VISIBLE);
if (drawable != null) {
ImageButton button = new ImageButton(getContext(), null, R.attr.menuItemSecondaryActionBarStyle);
button.setImageDrawable(drawable);
button.setContentDescription(label);
button.setOnClickListener(this);
button.setTag(count);
final int height = (int) (getResources().getDimension(R.dimen.menu_item_row_height));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, height);
params.weight = 1.0f;
button.setLayoutParams(params);
// Place action buttons to the right of the actual menu item
mActionButtons.add(button);
addView(button, count + 1);
}
}
protected int getActionButtonCount() {
return mActionButtons.size();
}
@Override
public void onClick(View view) {
for (View.OnClickListener listener : mActionButtonListeners) {
listener.onClick(view);
}
}
/**
* Update the styles if this view is being used in the context menus.
*
* Ideally, we just use different layout files and styles to set this, but
* MenuItemSwitcherLayout is too integrated into GeckoActionProvider to provide
* an easy separation so instead I provide this hack. I'm sorry.
*/
public void initContextMenuStyles() {
final int defaultContextMenuPadding = getContext().getResources().getDimensionPixelOffset(
R.dimen.context_menu_item_horizontal_padding);
mMenuItem.setPadding(defaultContextMenuPadding, getPaddingTop(),
defaultContextMenuPadding, getPaddingBottom());
}
}

View file

@ -0,0 +1,36 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
/**
* The outer container for the custom menu. On phones with h/w menu button,
* this is given to Android which inflates it to the right panel. On phones
* with s/w menu button, this is added to a MenuPopup.
*/
public class MenuPanel extends LinearLayout {
public MenuPanel(Context context, AttributeSet attrs) {
super(context, attrs);
int width = (int) context.getResources().getDimension(R.dimen.menu_item_row_width);
setLayoutParams(new ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT));
}
@Override
public boolean dispatchPopulateAccessibilityEvent (AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
}

View file

@ -0,0 +1,76 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.menu;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v7.widget.CardView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
/**
* A popup to show the inflated MenuPanel.
*/
public class MenuPopup extends PopupWindow {
private final CardView mPanel;
private final int mPopupWidth;
public MenuPopup(Context context) {
super(context);
setFocusable(true);
mPopupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_width);
// Setting a null background makes the popup to not close on touching outside.
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
LayoutInflater inflater = LayoutInflater.from(context);
mPanel = (CardView) inflater.inflate(R.layout.menu_popup, null);
setContentView(mPanel);
setAnimationStyle(R.style.PopupAnimation);
}
/**
* Adds the panel with the menu to its content.
*
* @param view The panel view with the menu to be shown.
*/
public void setPanelView(View view) {
view.setLayoutParams(new FrameLayout.LayoutParams(mPopupWidth,
FrameLayout.LayoutParams.WRAP_CONTENT));
mPanel.removeAllViews();
mPanel.addView(view);
}
/**
* A small little offset.
*/
@Override
public void showAsDropDown(View anchor) {
// Set a height, so that the popup will not be displayed below the bottom of the screen.
// We use the exact height of the internal content, which is the technique described in
// http://stackoverflow.com/a/7698709
setHeight(mPanel.getHeight());
// Attempt to align the center of the popup with the center of the anchor. If the anchor is
// near the edge of the screen, the popup will just align with the edge of the screen.
final int xOffset = anchor.getWidth() / 2 - mPopupWidth / 2;
showAsDropDown(anchor, xOffset, -anchor.getHeight());
}
}