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