/* * 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.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.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.ResourceHelper; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.InflateException; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.ActionMenuPresenter; import android.widget.FrameLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import java.util.ArrayList; /** * Creates the ActionBar as done by the framework. */ public class FrameworkActionBar extends BridgeActionBar { private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout"; // The Action Bar @NonNull private FrameworkActionBarWrapper mActionBar; // A fake parent for measuring views. @Nullable private ViewGroup mMeasureParent; /** * Inflate the action bar and attach it to {@code parentView} */ public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) { super(context, params); View decorContent = getDecorContent(); mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(), decorContent); FrameLayout contentRoot = (FrameLayout) decorContent.findViewById(android.R.id.content); // If something went wrong and we were not able to initialize the content root, // just add a frame layout inside this and return. if (contentRoot == null) { contentRoot = new FrameLayout(context); setMatchParent(contentRoot); if (mEnclosingLayout != null) { mEnclosingLayout.addView(contentRoot); } setContentRoot(contentRoot); } else { setContentRoot(contentRoot); setupActionBar(); mActionBar.inflateMenus(); } } @Override protected ResourceValue getLayoutResource(BridgeContext context) { ResourceValue layoutName = context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true); if (layoutName != null) { // We may need to resolve the reference obtained. layoutName = context.getRenderResources().findResValue(layoutName.getValue(), layoutName.isFramework()); } if (layoutName == null) { throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME + ") in the current theme."); } return layoutName; } @Override protected void setupActionBar() { super.setupActionBar(); mActionBar.setupActionBar(); } @Override protected void setHomeAsUp(boolean homeAsUp) { mActionBar.setHomeAsUp(homeAsUp); } @Override protected void setTitle(CharSequence title) { mActionBar.setTitle(title); } @Override protected void setSubtitle(CharSequence subtitle) { mActionBar.setSubTitle(subtitle); } @Override protected void setIcon(String icon) { mActionBar.setIcon(icon); } /** * 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. */ @Override public void createMenuPopup() { if (!isOverflowPopupNeeded()) { return; } DisplayMetrics metrics = mBridgeContext.getMetrics(); MenuBuilder menu = mActionBar.getMenuBuilder(); OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext()); ListView listView = new ListView(mActionBar.getPopupContext(), null, R.attr.dropDownListViewStyle); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( measureContentWidth(adapter), LayoutParams.WRAP_CONTENT); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); if (mActionBar.isSplit()) { layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); } else { layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); } layoutParams.setMarginEnd(getPixelValue("5dp", metrics)); listView.setLayoutParams(layoutParams); listView.setAdapter(adapter); final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null, R.styleable.PopupWindow, R.attr.popupMenuStyle, 0); listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); listView.setDivider(a.getDrawable(R.attr.actionBarDivider)); a.recycle(); listView.setElevation(mActionBar.getMenuPopupElevation()); assert mEnclosingLayout != null : "Unable to find view to attach ActionMenuPopup."; mEnclosingLayout.addView(listView); } private boolean isOverflowPopupNeeded() { boolean needed = mActionBar.isOverflowPopupNeeded(); if (!needed) { return false; } // Copied from android.widget.ActionMenuPresenter.updateMenuView() ArrayList menus = mActionBar.getMenuBuilder().getNonActionItems(); ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter(); if (presenter == null) { throw new RuntimeException("Failed to create a Presenter for Action Bar Menus."); } if (presenter.isOverflowReserved() && menus != null) { final int count = menus.size(); if (count == 1) { needed = !menus.get(0).isActionViewExpanded(); } else { needed = count > 0; } } return needed; } // 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; Context context = mActionBar.getPopupContext(); 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(context); } itemView = adapter.getView(i, itemView, mMeasureParent); itemView.measure(widthMeasureSpec, heightMeasureSpec); final int itemWidth = itemView.getMeasuredWidth(); int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2, context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth)); if (itemWidth >= popupMaxWidth) { return popupMaxWidth; } else if (itemWidth > maxWidth) { maxWidth = itemWidth; } } return maxWidth; } static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); return (int) typedValue.getDimension(metrics); } // TODO: This is duplicated from RenderSessionImpl. private int getActionBarHeight() { RenderResources resources = mBridgeContext.getRenderResources(); DisplayMetrics metrics = mBridgeContext.getMetrics(); ResourceValue value = resources.findItemInTheme("actionBarSize", true); // resolve it value = resources.resolveResValue(value); if (value != null) { // get the numerical value, if available TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), true); if (typedValue != null) { // compute the pixel value based on the display metrics return (int) typedValue.getDimension(metrics); } } return 0; } }