blob: 230094e2c2576c90c8f2276b90346fa444451a3a [file] [log] [blame]
/*
* 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.List;
/**
* 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(
BridgeContext.createFrameworkAttrReference(LAYOUT_ATTR_NAME));
if (layoutName != null) {
// We may need to resolve the reference obtained.
layoutName = context.getRenderResources().dereference(layoutName);
}
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(ResourceValue 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()
List<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
if (presenter == null) {
assert false : "Failed to create a Presenter for Action Bar Menus.";
return false;
}
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(
BridgeContext.createFrameworkAttrReference("actionBarSize"));
// 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;
}
}