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