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