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.content.res.ColorStateList;
37import android.graphics.Bitmap;
38import android.graphics.Bitmap_Delegate;
39import android.graphics.drawable.BitmapDrawable;
40import android.graphics.drawable.Drawable;
41import android.util.TypedValue;
42import android.view.Gravity;
43import android.view.LayoutInflater;
44import android.view.View;
45import android.widget.ImageView;
46import android.widget.LinearLayout;
47import android.widget.TextView;
48
49import java.io.IOException;
50import java.io.InputStream;
51
52import 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 */
63abstract class CustomBar extends LinearLayout {
64
65
66    private final int mSimulatedPlatformVersion;
67
68    protected abstract TextView getStyleableTextView();
69
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
101    protected void loadIcon(int index, String iconName, Density density) {
102        loadIcon(index, iconName, density, false);
103    }
104
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
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
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
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
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     */
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
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
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