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