FrameworkActionBar.java revision e5afc3117be394fdd92496b39e9bad248972902a
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.layoutlib.bridge.bars;
18
19import com.android.ide.common.rendering.api.RenderResources;
20import com.android.ide.common.rendering.api.ResourceValue;
21import com.android.ide.common.rendering.api.SessionParams;
22import com.android.internal.R;
23import com.android.internal.view.menu.MenuBuilder;
24import com.android.internal.view.menu.MenuItemImpl;
25import com.android.layoutlib.bridge.android.BridgeContext;
26import com.android.layoutlib.bridge.impl.ResourceHelper;
27
28import android.annotation.NonNull;
29import android.annotation.Nullable;
30import android.content.Context;
31import android.content.res.TypedArray;
32import android.util.DisplayMetrics;
33import android.util.TypedValue;
34import android.view.InflateException;
35import android.view.View;
36import android.view.View.MeasureSpec;
37import android.view.ViewGroup;
38import android.view.ViewGroup.LayoutParams;
39import android.widget.ActionMenuPresenter;
40import android.widget.FrameLayout;
41import android.widget.ListAdapter;
42import android.widget.ListView;
43import android.widget.RelativeLayout;
44
45import java.util.ArrayList;
46
47/**
48 * Creates the ActionBar as done by the framework.
49 */
50public class FrameworkActionBar extends BridgeActionBar {
51
52    private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
53
54    // The Action Bar
55    @NonNull private FrameworkActionBarWrapper mActionBar;
56
57    // A fake parent for measuring views.
58    @Nullable private ViewGroup mMeasureParent;
59
60    /**
61     * Inflate the action bar and attach it to {@code parentView}
62     */
63    public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
64        super(context, params);
65
66        View decorContent = getDecorContent();
67
68        mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(),
69                decorContent);
70
71        FrameLayout contentRoot = (FrameLayout) decorContent.findViewById(android.R.id.content);
72
73        // If something went wrong and we were not able to initialize the content root,
74        // just add a frame layout inside this and return.
75        if (contentRoot == null) {
76            contentRoot = new FrameLayout(context);
77            setMatchParent(contentRoot);
78            if (mEnclosingLayout != null) {
79                mEnclosingLayout.addView(contentRoot);
80            }
81            setContentRoot(contentRoot);
82        } else {
83            setContentRoot(contentRoot);
84            setupActionBar();
85            mActionBar.inflateMenus();
86        }
87    }
88
89    @Override
90    protected ResourceValue getLayoutResource(BridgeContext context) {
91        ResourceValue layoutName =
92                context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true);
93        if (layoutName != null) {
94            // We may need to resolve the reference obtained.
95            layoutName = context.getRenderResources().findResValue(layoutName.getValue(),
96                    layoutName.isFramework());
97        }
98        if (layoutName == null) {
99             throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME
100                    + ") in the current theme.");
101        }
102        return layoutName;
103    }
104
105    @Override
106    protected void setupActionBar() {
107        super.setupActionBar();
108        mActionBar.setupActionBar();
109    }
110
111    @Override
112    protected void setHomeAsUp(boolean homeAsUp) {
113        mActionBar.setHomeAsUp(homeAsUp);
114    }
115
116    @Override
117    protected void setTitle(CharSequence title) {
118        mActionBar.setTitle(title);
119    }
120
121    @Override
122    protected void setSubtitle(CharSequence subtitle) {
123        mActionBar.setSubTitle(subtitle);
124    }
125
126    @Override
127    protected void setIcon(String icon) {
128        mActionBar.setIcon(icon);
129    }
130
131    /**
132     * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
133     * the content frame which shall serve as the new content root.
134     */
135    @Override
136    public void createMenuPopup() {
137        if (!isOverflowPopupNeeded()) {
138            return;
139        }
140
141        DisplayMetrics metrics = mBridgeContext.getMetrics();
142        MenuBuilder menu = mActionBar.getMenuBuilder();
143        OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
144
145        ListView listView = new ListView(mActionBar.getPopupContext(), null,
146                R.attr.dropDownListViewStyle);
147        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
148                measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
149        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
150        if (mActionBar.isSplit()) {
151            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
152            layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
153        } else {
154            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
155            layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
156        }
157        layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
158        listView.setLayoutParams(layoutParams);
159        listView.setAdapter(adapter);
160        final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
161                R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
162        listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
163        listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
164        a.recycle();
165        listView.setElevation(mActionBar.getMenuPopupElevation());
166        assert mEnclosingLayout != null : "Unable to find view to attach ActionMenuPopup.";
167        mEnclosingLayout.addView(listView);
168    }
169
170    private boolean isOverflowPopupNeeded() {
171        boolean needed = mActionBar.isOverflowPopupNeeded();
172        if (!needed) {
173            return false;
174        }
175        // Copied from android.widget.ActionMenuPresenter.updateMenuView()
176        ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
177        ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
178        if (presenter == null) {
179            throw new RuntimeException("Failed to create a Presenter for Action Bar Menus.");
180        }
181        if (presenter.isOverflowReserved() &&
182                menus != null) {
183            final int count = menus.size();
184            if (count == 1) {
185                needed = !menus.get(0).isActionViewExpanded();
186            } else {
187                needed = count > 0;
188            }
189        }
190        return needed;
191    }
192
193    // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
194    private int measureContentWidth(@NonNull ListAdapter adapter) {
195        // Menus don't tend to be long, so this is more sane than it looks.
196        int maxWidth = 0;
197        View itemView = null;
198        int itemType = 0;
199
200        Context context = mActionBar.getPopupContext();
201        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
202        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
203        final int count = adapter.getCount();
204        for (int i = 0; i < count; i++) {
205            final int positionType = adapter.getItemViewType(i);
206            if (positionType != itemType) {
207                itemType = positionType;
208                itemView = null;
209            }
210
211            if (mMeasureParent == null) {
212                mMeasureParent = new FrameLayout(context);
213            }
214
215            itemView = adapter.getView(i, itemView, mMeasureParent);
216            itemView.measure(widthMeasureSpec, heightMeasureSpec);
217
218            final int itemWidth = itemView.getMeasuredWidth();
219            int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
220                    context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth));
221            if (itemWidth >= popupMaxWidth) {
222                return popupMaxWidth;
223            } else if (itemWidth > maxWidth) {
224                maxWidth = itemWidth;
225            }
226        }
227
228        return maxWidth;
229    }
230
231    static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
232        TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
233        return (int) typedValue.getDimension(metrics);
234    }
235
236    // TODO: This is duplicated from RenderSessionImpl.
237    private int getActionBarHeight() {
238        RenderResources resources = mBridgeContext.getRenderResources();
239        DisplayMetrics metrics = mBridgeContext.getMetrics();
240        ResourceValue value = resources.findItemInTheme("actionBarSize", true);
241
242        // resolve it
243        value = resources.resolveResValue(value);
244
245        if (value != null) {
246            // get the numerical value, if available
247            TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
248                    true);
249            if (typedValue != null) {
250                // compute the pixel value based on the display metrics
251                return (int) typedValue.getDimension(metrics);
252
253            }
254        }
255        return 0;
256    }
257}
258