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

Commit 56759be8 authored by Deepanshu Gupta's avatar Deepanshu Gupta
Browse files

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

In addition to the cherrypicked changes, appropriate changes due to
changes to com.android.internal.widget.ActionBarView are made.

The icon used in the Action Bar may not always be the right one due to
the above resolution.

Change-Id: Ib7cc314079099c010f7d53849e204db36c410357
(cherry-picked from commit 1b87390c)
parent fe38489f
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;
    }
}
+246 −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,62 @@ 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);
        }

        // Ideally we should get the icon from the mThemedContext using R.styleable.ActionBar_icon.
        // But for simplicity, we are using the icon passed from the session params. This has been
        // fixed in API 19.
        if (mIcon != null) {
            Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/);
            if (iconDrawable != null) {
                mActionBar.setIcon(iconDrawable);
            }
        }

    private void setUpActionMenus() {
        if (mMenuIds == null) {
        if (mActionBarView != 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 +215,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