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 17 package com.android.layoutlib.bridge.bars; 18 19 import com.android.ide.common.rendering.api.RenderResources; 20 import com.android.ide.common.rendering.api.ResourceValue; 21 import com.android.ide.common.rendering.api.SessionParams; 22 import com.android.internal.R; 23 import com.android.internal.view.menu.MenuBuilder; 24 import com.android.internal.view.menu.MenuItemImpl; 25 import com.android.layoutlib.bridge.android.BridgeContext; 26 import com.android.layoutlib.bridge.impl.ResourceHelper; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.content.Context; 31 import android.content.res.TypedArray; 32 import android.util.DisplayMetrics; 33 import android.util.TypedValue; 34 import android.view.InflateException; 35 import android.view.View; 36 import android.view.View.MeasureSpec; 37 import android.view.ViewGroup; 38 import android.view.ViewGroup.LayoutParams; 39 import android.widget.ActionMenuPresenter; 40 import android.widget.FrameLayout; 41 import android.widget.ListAdapter; 42 import android.widget.ListView; 43 import android.widget.RelativeLayout; 44 45 import java.util.ArrayList; 46 47 /** 48 * Creates the ActionBar as done by the framework. 49 */ 50 public 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 assert false : "Failed to create a Presenter for Action Bar Menus."; 180 return false; 181 } 182 if (presenter.isOverflowReserved() && 183 menus != null) { 184 final int count = menus.size(); 185 if (count == 1) { 186 needed = !menus.get(0).isActionViewExpanded(); 187 } else { 188 needed = count > 0; 189 } 190 } 191 return needed; 192 } 193 194 // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth() 195 private int measureContentWidth(@NonNull ListAdapter adapter) { 196 // Menus don't tend to be long, so this is more sane than it looks. 197 int maxWidth = 0; 198 View itemView = null; 199 int itemType = 0; 200 201 Context context = mActionBar.getPopupContext(); 202 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 203 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 204 final int count = adapter.getCount(); 205 for (int i = 0; i < count; i++) { 206 final int positionType = adapter.getItemViewType(i); 207 if (positionType != itemType) { 208 itemType = positionType; 209 itemView = null; 210 } 211 212 if (mMeasureParent == null) { 213 mMeasureParent = new FrameLayout(context); 214 } 215 216 itemView = adapter.getView(i, itemView, mMeasureParent); 217 itemView.measure(widthMeasureSpec, heightMeasureSpec); 218 219 final int itemWidth = itemView.getMeasuredWidth(); 220 int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2, 221 context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth)); 222 if (itemWidth >= popupMaxWidth) { 223 return popupMaxWidth; 224 } else if (itemWidth > maxWidth) { 225 maxWidth = itemWidth; 226 } 227 } 228 229 return maxWidth; 230 } 231 232 static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { 233 TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); 234 return (int) typedValue.getDimension(metrics); 235 } 236 237 // TODO: This is duplicated from RenderSessionImpl. 238 private int getActionBarHeight() { 239 RenderResources resources = mBridgeContext.getRenderResources(); 240 DisplayMetrics metrics = mBridgeContext.getMetrics(); 241 ResourceValue value = resources.findItemInTheme("actionBarSize", true); 242 243 // resolve it 244 value = resources.resolveResValue(value); 245 246 if (value != null) { 247 // get the numerical value, if available 248 TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), 249 true); 250 if (typedValue != null) { 251 // compute the pixel value based on the display metrics 252 return (int) typedValue.getDimension(metrics); 253 254 } 255 } 256 return 0; 257 } 258 } 259