1 /* 2 * Copyright (C) 2011 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.LayoutLog; 20 import com.android.ide.common.rendering.api.RenderResources; 21 import com.android.ide.common.rendering.api.ResourceValue; 22 import com.android.ide.common.rendering.api.StyleResourceValue; 23 import com.android.layoutlib.bridge.Bridge; 24 import com.android.layoutlib.bridge.android.BridgeContext; 25 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 26 import com.android.layoutlib.bridge.impl.ParserFactory; 27 import com.android.layoutlib.bridge.impl.ResourceHelper; 28 import com.android.resources.Density; 29 import com.android.resources.LayoutDirection; 30 import com.android.resources.ResourceType; 31 32 import org.xmlpull.v1.XmlPullParser; 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import android.annotation.NonNull; 36 import android.content.res.ColorStateList; 37 import android.graphics.Bitmap; 38 import android.graphics.Bitmap_Delegate; 39 import android.graphics.drawable.BitmapDrawable; 40 import android.graphics.drawable.Drawable; 41 import android.util.TypedValue; 42 import android.view.Gravity; 43 import android.view.LayoutInflater; 44 import android.view.View; 45 import android.widget.ImageView; 46 import android.widget.LinearLayout; 47 import android.widget.TextView; 48 49 import java.io.IOException; 50 import java.io.InputStream; 51 52 import static android.os._Original_Build.VERSION_CODES.LOLLIPOP; 53 54 /** 55 * Base "bar" class for the window decor around the the edited layout. 56 * This is basically an horizontal layout that loads a given layout on creation (it is read 57 * through {@link Class#getResourceAsStream(String)}). 58 * 59 * The given layout should be a merge layout so that all the children belong to this class directly. 60 * 61 * It also provides a few utility methods to configure the content of the layout. 62 */ 63 abstract class CustomBar extends LinearLayout { 64 65 66 private final int mSimulatedPlatformVersion; 67 getStyleableTextView()68 protected abstract TextView getStyleableTextView(); 69 CustomBar(BridgeContext context, int orientation, String layoutPath, String name, int simulatedPlatformVersion)70 protected CustomBar(BridgeContext context, int orientation, String layoutPath, 71 String name, int simulatedPlatformVersion) { 72 super(context); 73 mSimulatedPlatformVersion = simulatedPlatformVersion; 74 setOrientation(orientation); 75 if (orientation == LinearLayout.HORIZONTAL) { 76 setGravity(Gravity.CENTER_VERTICAL); 77 } else { 78 setGravity(Gravity.CENTER_HORIZONTAL); 79 } 80 81 LayoutInflater inflater = LayoutInflater.from(mContext); 82 83 XmlPullParser parser; 84 try { 85 parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath), name); 86 87 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(parser, context, false); 88 89 try { 90 inflater.inflate(bridgeParser, this, true); 91 } finally { 92 bridgeParser.ensurePopped(); 93 } 94 } catch (XmlPullParserException e) { 95 // Should not happen as the resource is bundled with the jar, and ParserFactory should 96 // have been initialized. 97 assert false; 98 } 99 } 100 loadIcon(int index, String iconName, Density density)101 protected void loadIcon(int index, String iconName, Density density) { 102 loadIcon(index, iconName, density, false); 103 } 104 loadIcon(int index, String iconName, Density density, boolean isRtl)105 protected void loadIcon(int index, String iconName, Density density, boolean isRtl) { 106 View child = getChildAt(index); 107 if (child instanceof ImageView) { 108 ImageView imageView = (ImageView) child; 109 110 LayoutDirection dir = isRtl ? LayoutDirection.RTL : null; 111 IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion, 112 dir); 113 InputStream stream = iconLoader.getIcon(); 114 115 if (stream != null) { 116 density = iconLoader.getDensity(); 117 String path = iconLoader.getPath(); 118 // look for a cached bitmap 119 Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/); 120 if (bitmap == null) { 121 try { 122 bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); 123 Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/); 124 } catch (IOException e) { 125 return; 126 } 127 } 128 129 if (bitmap != null) { 130 BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), 131 bitmap); 132 imageView.setImageDrawable(drawable); 133 } 134 } 135 } 136 } 137 setText(int index, String string, boolean reference)138 protected TextView setText(int index, String string, boolean reference) { 139 View child = getChildAt(index); 140 if (child instanceof TextView) { 141 TextView textView = (TextView) child; 142 setText(textView, string, reference); 143 return textView; 144 } 145 146 return null; 147 } 148 setText(TextView textView, String string, boolean reference)149 private void setText(TextView textView, String string, boolean reference) { 150 if (reference) { 151 ResourceValue value = getResourceValue(string); 152 if (value != null) { 153 string = value.getValue(); 154 } 155 } 156 textView.setText(string); 157 } 158 setStyle(String themeEntryName)159 protected void setStyle(String themeEntryName) { 160 161 BridgeContext bridgeContext = getContext(); 162 RenderResources res = bridgeContext.getRenderResources(); 163 164 ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/); 165 value = res.resolveResValue(value); 166 167 if (!(value instanceof StyleResourceValue)) { 168 return; 169 } 170 171 StyleResourceValue style = (StyleResourceValue) value; 172 173 // get the background 174 ResourceValue backgroundValue = res.findItemInStyle(style, "background", 175 true /*isFrameworkAttr*/); 176 backgroundValue = res.resolveResValue(backgroundValue); 177 if (backgroundValue != null) { 178 Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext); 179 if (d != null) { 180 setBackground(d); 181 } 182 } 183 184 TextView textView = getStyleableTextView(); 185 if (textView != null) { 186 // get the text style 187 ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle", 188 true /*isFrameworkAttr*/); 189 textStyleValue = res.resolveResValue(textStyleValue); 190 if (textStyleValue instanceof StyleResourceValue) { 191 StyleResourceValue textStyle = (StyleResourceValue) textStyleValue; 192 193 ResourceValue textSize = res.findItemInStyle(textStyle, "textSize", 194 true /*isFrameworkAttr*/); 195 textSize = res.resolveResValue(textSize); 196 197 if (textSize != null) { 198 TypedValue out = new TypedValue(); 199 if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out, 200 true /*requireUnit*/)) { 201 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 202 out.getDimension(bridgeContext.getResources().getDisplayMetrics())); 203 } 204 } 205 206 207 ResourceValue textColor = res.findItemInStyle(textStyle, "textColor", 208 true); 209 textColor = res.resolveResValue(textColor); 210 if (textColor != null) { 211 ColorStateList stateList = ResourceHelper.getColorStateList( 212 textColor, bridgeContext); 213 if (stateList != null) { 214 textView.setTextColor(stateList); 215 } 216 } 217 } 218 } 219 } 220 221 @Override getContext()222 public BridgeContext getContext() { 223 return (BridgeContext) mContext; 224 } 225 226 /** 227 * Find the background color for this bar from the theme attributes. Only relevant to StatusBar 228 * and NavigationBar. 229 * <p/> 230 * Returns 0 if not found. 231 * 232 * @param colorAttrName the attribute name for the background color 233 * @param translucentAttrName the attribute name for the translucency property of the bar. 234 * 235 * @throws NumberFormatException if color resolved to an invalid string. 236 */ getBarColor(@onNull String colorAttrName, @NonNull String translucentAttrName)237 protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) { 238 if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) { 239 return 0; 240 } 241 RenderResources renderResources = getContext().getRenderResources(); 242 // First check if the bar is translucent. 243 boolean translucent = ResourceHelper.getBooleanThemeValue(renderResources, 244 translucentAttrName, true, false); 245 if (translucent) { 246 // Keep in sync with R.color.system_bar_background_semi_transparent from system ui. 247 return 0x66000000; // 40% black. 248 } 249 boolean transparent = ResourceHelper.getBooleanThemeValue(renderResources, 250 "windowDrawsSystemBarBackgrounds", true, false); 251 if (transparent) { 252 return getColor(renderResources, colorAttrName); 253 } 254 return 0; 255 } 256 getColor(RenderResources renderResources, String attr)257 private static int getColor(RenderResources renderResources, String attr) { 258 // From ?attr/foo to @color/bar. This is most likely an ItemResourceValue. 259 ResourceValue resource = renderResources.findItemInTheme(attr, true); 260 // Form @color/bar to the #AARRGGBB 261 resource = renderResources.resolveResValue(resource); 262 if (resource != null) { 263 ResourceType type = resource.getResourceType(); 264 if (type == null || type == ResourceType.COLOR) { 265 // if no type is specified, the value may have been specified directly in the style 266 // file, rather than referencing a color resource value. 267 try { 268 return ResourceHelper.getColor(resource.getValue()); 269 } catch (NumberFormatException e) { 270 // Conversion failed. 271 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 272 "Theme attribute @android:" + attr + 273 " does not reference a color, instead is '" + 274 resource.getValue() + "'.", resource); 275 } 276 } 277 } 278 return 0; 279 } 280 getResourceValue(String reference)281 private ResourceValue getResourceValue(String reference) { 282 RenderResources res = getContext().getRenderResources(); 283 284 // find the resource 285 ResourceValue value = res.findResValue(reference, false); 286 287 // resolve it if needed 288 return res.resolveResValue(value); 289 } 290 } 291