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