Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b87390c authored by Deepanshu Gupta's avatar Deepanshu Gupta
Browse files

Overflow menu popup for Action Bar in Layoutlib [DO NOT MERGE]

Adds MenuBuilderAccessor in addition to the cherry-picked changes.

Change-Id: Ib7cc314079099c010f7d53849e204db36c410357
(cherry-picked from commit 929eea6b)
parent 4ccc4bd5
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

<include layout="@android:layout/screen_action_bar" />

</merge>
+12 −0
Original line number Diff line number Diff line
package com.android.internal.view.menu;

import java.util.ArrayList;

/**
 * To access non public members of {@link MenuBuilder}
 */
public class MenuBuilderAccessor {
    public static ArrayList<MenuItemImpl> getNonActionItems(MenuBuilder builder) {
        return builder.getNonActionItems();
    }
}
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.widget;

import com.android.internal.view.menu.ActionMenuPresenter;

/**
 * To access non public members of AbsActionBarView
 */
public class ActionBarAccessor {

    /**
     * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView}
     */
    public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) {
        return view.mActionMenuPresenter;
    }
}
+245 −80
Original line number Diff line number Diff line
@@ -16,108 +16,123 @@

package com.android.layoutlib.bridge.bars;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.internal.R;
import com.android.internal.app.ActionBarImpl;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuBuilderAccessor;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.widget.ActionBarAccessor;
import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarView;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;

import java.util.ArrayList;
import java.util.List;

/**
 * A layout representing the action bar.
 */
public class ActionBarLayout extends LinearLayout {

    // Store another reference to the context so that we don't have to cast it repeatedly.
    @SuppressWarnings("hiding")
    private final BridgeContext mContext;
    private final ActionBar mActionBar;
    private final String mIcon;
    private final String mTitle;
    private final String mSubTitle;
    @NonNull private final BridgeContext mBridgeContext;
    @NonNull private final Context mThemedContext;

    @NonNull private final ActionBar mActionBar;

    // Data for Action Bar.
    @Nullable private final String mIcon;
    @Nullable private final String mTitle;
    @Nullable private final String mSubTitle;
    private final boolean mSplit;
    private final boolean mShowHomeAsUp;
    private final List<Integer> mMenuIds;
    private final MenuBuilder mMenuBuilder;
    private final int mNavMode;

    public ActionBarLayout(BridgeContext context, SessionParams params)
            throws XmlPullParserException {
        super(context);
        mIcon = params.getAppIcon();
        mTitle = params.getAppLabel();
        mContext = context;
        mMenuIds = new ArrayList<Integer>();

        ActionBarCallback callback = params.getProjectCallback().getActionBarCallback();
        mSplit = callback.getSplitActionBarWhenNarrow() &
                context.getResources().getBoolean(
                        com.android.internal.R.bool.split_action_bar_is_narrow);
        mNavMode = callback.getNavigationMode();
        // TODO: Support Navigation Drawer Indicator.
        mShowHomeAsUp = callback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP;
        mSubTitle = callback.getSubTitle();
    // Helper fields.
    @NonNull private final MenuBuilder mMenuBuilder;
    private final int mPopupMaxWidth;
    @NonNull private final RenderResources res;
    @Nullable private final ActionBarView mActionBarView;
    @Nullable private FrameLayout mContentRoot;
    @NonNull private final ActionBarCallback mCallback;

        fillMenuIds(callback.getMenuIdNames());
        mMenuBuilder = new MenuBuilder(mContext);
    // A fake parent for measuring views.
    @Nullable private ViewGroup mMeasureParent;

    public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) {

        super(context);
        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        XmlPullParser parser = ParserFactory.create(
                getClass().getResourceAsStream("/bars/action_bar.xml"), "action_bar.xml");
        BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
                parser, context, false /*platformFile*/);
        try {
            inflater.inflate(bridgeParser, this, true /*attachToRoot*/);
        } finally {
            bridgeParser.ensurePopped();
        }
        // Inflate action bar layout.
        LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this,
                true /*attachToRoot*/);
        mActionBar = new ActionBarImpl(this);

        setUpActionBar();
    }
        // Set contexts.
        mBridgeContext = context;
        mThemedContext = mActionBar.getThemedContext();

    private void fillMenuIds(List<String> menuIdNames) {
        for (String name : menuIdNames) {
            int id = mContext.getProjectResourceValue(ResourceType.MENU, name, -1);
            if (id > -1) {
                mMenuIds.add(id);
            }
        }
    }
        // Set data for action bar.
        mCallback = params.getProjectCallback().getActionBarCallback();
        mIcon = params.getAppIcon();
        mTitle = params.getAppLabel();
        // Split Action Bar when the screen size is narrow and the application requests split action
        // bar when narrow.
        mSplit = context.getResources().getBoolean(R.bool.split_action_bar_is_narrow) &&
                mCallback.getSplitActionBarWhenNarrow();
        mNavMode = mCallback.getNavigationMode();
        // TODO: Support Navigation Drawer Indicator.
        mShowHomeAsUp = mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP;
        mSubTitle = mCallback.getSubTitle();

    private void setUpActionBar() {
        RenderResources res = mContext.getRenderResources();
        Drawable iconDrawable = getDrawable(res, mIcon, false /*isFramework*/);
        if (iconDrawable != null) {
            mActionBar.setIcon(iconDrawable);

        // Set helper fields.
        mMenuBuilder = new MenuBuilder(mThemedContext);
        res = mBridgeContext.getRenderResources();
        mPopupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
                mThemedContext.getResources().getDimensionPixelSize(
                        R.dimen.config_prefDialogWidth));
        mActionBarView = (ActionBarView) findViewById(R.id.action_bar);
        mContentRoot = (FrameLayout) findViewById(android.R.id.content);

        setupActionBar();
    }

    /**
     * Sets up the action bar by filling the appropriate data.
     */
    private void setupActionBar() {
        // Add title and sub title.
        ResourceValue titleValue = res.findResValue(mTitle, false /*isFramework*/);
        if (titleValue != null && titleValue.getValue() != null) {
            mActionBar.setTitle(titleValue.getValue());
@@ -127,41 +142,61 @@ public class ActionBarLayout extends LinearLayout {
        if (mSubTitle != null) {
            mActionBar.setSubtitle(mSubTitle);
        }
        ActionBarView actionBarView = (ActionBarView) findViewById(
            Bridge.getResourceId(ResourceType.ID, "action_bar"));
        actionBarView.setSplitView((ActionBarContainer) findViewById(
            Bridge.getResourceId(ResourceType.ID, "split_action_bar")));
        actionBarView.setSplitActionBar(mSplit);

        // Add show home as up icon.
        if (mShowHomeAsUp) {
            mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP);
        }
        setUpActionMenus();

        // Set the navigation mode.
        mActionBar.setNavigationMode(mNavMode);
        if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) {
            setUpTabs(3);
            setupTabs(3);
        }

        if (mActionBarView != null) {
            // If the action bar style doesn't specify an icon, set the icon obtained from the session
            // params.
            if (!mActionBarView.hasIcon() && mIcon != null) {
                Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/);
                if (iconDrawable != null) {
                    mActionBar.setIcon(iconDrawable);
                }
            }

    private void setUpActionMenus() {
        if (mMenuIds == null) {
            // Set action bar to be split, if needed.
            mActionBarView.setSplitView((ActionBarContainer) findViewById(R.id.split_action_bar));
            mActionBarView.setSplitActionBar(mSplit);

            inflateMenus();
        }
    }

    /**
     * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
     * adds them to the action bar.
     */
    private void inflateMenus() {
        if (mActionBarView == null) {
            return;
        }
        ActionBarView actionBarView = (ActionBarView) findViewById(Bridge.getResourceId(
                ResourceType.ID, "action_bar"));
        final MenuInflater inflater = new MenuInflater(mActionBar.getThemedContext());
        for (int id : mMenuIds) {
        final MenuInflater inflater = new MenuInflater(mThemedContext);
        for (String name : mCallback.getMenuIdNames()) {
            if (mBridgeContext.getRenderResources().getProjectResource(ResourceType.MENU, name)
                    != null) {
                int id = mBridgeContext.getProjectResourceValue(ResourceType.MENU, name, -1);
                if (id > -1) {
                    inflater.inflate(id, mMenuBuilder);
                }
        actionBarView.setMenu(mMenuBuilder, null /*callback*/);
            }
        }
        mActionBarView.setMenu(mMenuBuilder, null /*callback*/);
    }

    // TODO: Use an adapter, like List View to set up tabs.
    private void setUpTabs(int num) {
    private void setupTabs(int num) {
        for (int i = 1; i <= num; i++) {
            Tab tab = mActionBar.newTab()
                    .setText("Tab" + i)
                    .setTabListener(new TabListener() {
            Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
                @Override
                public void onTabUnselected(Tab t, FragmentTransaction ft) {
                    // pass
@@ -179,12 +214,142 @@ public class ActionBarLayout extends LinearLayout {
        }
    }

    private Drawable getDrawable(RenderResources res, String name, boolean isFramework) {
    @Nullable
    private Drawable getDrawable(@NonNull String name, boolean isFramework) {
        ResourceValue value = res.findResValue(name, isFramework);
        value = res.resolveResValue(value);
        if (value != null) {
            return ResourceHelper.getDrawable(value, mContext);
            return ResourceHelper.getDrawable(value, mBridgeContext);
        }
        return null;
    }

    /**
     * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
     * the content frame which shall serve as the new content root.
     */
    public void createMenuPopup() {
        assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot
                : "Action Bar Menus have already been created.";

        if (!isOverflowPopupNeeded()) {
            return;
        }

        // Create a layout to hold the menus and the user's content.
        RelativeLayout layout = new RelativeLayout(mThemedContext);
        layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT));
        mContentRoot.addView(layout);
        // Create a layout for the user's content.
        FrameLayout contentRoot = new FrameLayout(mBridgeContext);
        contentRoot.setLayoutParams(new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        // Add contentRoot and menus to the layout.
        layout.addView(contentRoot);
        layout.addView(createMenuView());
        // ContentRoot is now the view we just created.
        mContentRoot = contentRoot;
    }

    /**
     * Returns a {@link LinearLayout} containing the menu list view to be embedded in a
     * {@link RelativeLayout}
     */
    @NonNull
    private View createMenuView() {
        DisplayMetrics metrics = mBridgeContext.getMetrics();
        OverflowMenuAdapter adapter = new OverflowMenuAdapter(mMenuBuilder, mThemedContext);

        LinearLayout layout = new LinearLayout(mThemedContext);
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
        if (mSplit) {
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            // TODO: Find correct value instead of hardcoded 10dp.
            layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
        } else {
            layoutParams.topMargin = getPixelValue("-10dp", metrics);
        }
        layout.setLayoutParams(layoutParams);
        final TypedArray a = mThemedContext.obtainStyledAttributes(null,
                R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
        layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
        layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider));
        a.recycle();
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.setDividerPadding(getPixelValue("12dp", metrics));
        layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);

        ListView listView = new ListView(mThemedContext, null, R.attr.dropDownListViewStyle);
        listView.setAdapter(adapter);
        layout.addView(listView);
        return layout;
    }

    private boolean isOverflowPopupNeeded() {
        boolean needed = mCallback.isOverflowPopupNeeded();
        if (!needed) {
            return false;
        }
        // Copied from android.widget.ActionMenuPresenter.updateMenuView()
        ArrayList<MenuItemImpl> menus = MenuBuilderAccessor.getNonActionItems(mMenuBuilder);
        if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() &&
                menus != null) {
            final int count = menus.size();
            if (count == 1) {
                needed = !menus.get(0).isActionViewExpanded();
            } else {
                needed = count > 0;
            }
        }
        return needed;
    }

    @Nullable
    public FrameLayout getContentRoot() {
        return mContentRoot;
    }

    // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
    private int measureContentWidth(@NonNull ListAdapter adapter) {
        // Menus don't tend to be long, so this is more sane than it looks.
        int maxWidth = 0;
        View itemView = null;
        int itemType = 0;

        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        final int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            final int positionType = adapter.getItemViewType(i);
            if (positionType != itemType) {
                itemType = positionType;
                itemView = null;
            }

            if (mMeasureParent == null) {
                mMeasureParent = new FrameLayout(mThemedContext);
            }

            itemView = adapter.getView(i, itemView, mMeasureParent);
            itemView.measure(widthMeasureSpec, heightMeasureSpec);

            final int itemWidth = itemView.getMeasuredWidth();
            if (itemWidth >= mPopupMaxWidth) {
                return mPopupMaxWidth;
            } else if (itemWidth > maxWidth) {
                maxWidth = itemWidth;
            }
        }

        return maxWidth;
    }

    private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
        TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
        return (int) typedValue.getDimension(metrics);
    }

}
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.layoutlib.bridge.bars;

import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuBuilderAccessor;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuView;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.util.ArrayList;

/**
 * Provides an adapter for Overflow menu popup. This is very similar to
 * {@code MenuPopupHelper.MenuAdapter}
 */
public class OverflowMenuAdapter extends BaseAdapter {

    private final MenuBuilder mMenu;
    private int mExpandedIndex = -1;
    private final Context context;

    public OverflowMenuAdapter(MenuBuilder menu, Context context) {
        mMenu = menu;
        findExpandedIndex();
        this.context = context;
    }

    @Override
    public int getCount() {
        ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
        if (mExpandedIndex < 0) {
            return items.size();
        }
        return items.size() - 1;
    }

    @Override
    public MenuItemImpl getItem(int position) {
        ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
        if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
            position++;
        }
        return items.get(position);
    }

    @Override
    public long getItemId(int position) {
        // Since a menu item's ID is optional, we'll use the position as an
        // ID for the item in the AdapterView
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            LayoutInflater mInflater = LayoutInflater.from(context);
            convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout,
                    parent, false);
        }

        MenuView.ItemView itemView = (MenuView.ItemView) convertView;
        itemView.initialize(getItem(position), 0);
        return convertView;
    }

    private void findExpandedIndex() {
        final MenuItemImpl expandedItem = mMenu.getExpandedItem();
        if (expandedItem != null) {
            final ArrayList<MenuItemImpl> items = MenuBuilderAccessor.getNonActionItems(mMenu);
            final int count = items.size();
            for (int i = 0; i < count; i++) {
                final MenuItemImpl item = items.get(i);
                if (item == expandedItem) {
                    mExpandedIndex = i;
                    return;
                }
            }
        }
    }
}
Loading