19d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes/*
29d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Copyright (C) 2015 The Android Open Source Project
39d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes *
49d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
59d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * you may not use this file except in compliance with the License.
69d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * You may obtain a copy of the License at
79d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes *
89d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
99d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes *
109d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Unless required by applicable law or agreed to in writing, software
119d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
129d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * See the License for the specific language governing permissions and
149d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * limitations under the License.
159d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes */
169d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banespackage android.support.v7.content.res;
189d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
199d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport org.xmlpull.v1.XmlPullParser;
209d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport org.xmlpull.v1.XmlPullParserException;
219d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
229d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.ColorStateList;
239d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.Resources;
249d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.TypedArray;
259d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.graphics.Color;
269d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.annotation.NonNull;
279d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.annotation.Nullable;
289d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.v4.graphics.ColorUtils;
299d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.v7.appcompat.R;
309d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.AttributeSet;
319d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.StateSet;
329d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.Xml;
339d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
349d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport java.io.IOException;
359d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
3615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesfinal class AppCompatColorStateListInflater {
379d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
389d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    private static final int DEFAULT_COLOR = Color.RED;
399d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
4015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes    private AppCompatColorStateListInflater() {}
4115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes
429d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    /**
439d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * Creates a ColorStateList from an XML document using given a set of
449d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * {@link Resources} and a {@link Theme}.
459d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     *
469d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @param r Resources against which the ColorStateList should be inflated.
479d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @param parser Parser for the XML document defining the ColorStateList.
489d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @param theme Optional theme to apply to the color state list, may be
499d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     *              {@code null}.
509d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @return A new color state list.
519d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     */
529d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    @NonNull
539d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
549d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            @Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
559d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        final AttributeSet attrs = Xml.asAttributeSet(parser);
569d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
579d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int type;
589d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        while ((type = parser.next()) != XmlPullParser.START_TAG
599d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                && type != XmlPullParser.END_DOCUMENT) {
609d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            // Seek parser to start tag.
619d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        }
629d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
639d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        if (type != XmlPullParser.START_TAG) {
649d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            throw new XmlPullParserException("No start tag found");
659d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        }
669d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
679d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        return createFromXmlInner(r, parser, attrs, theme);
689d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    }
699d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
709d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    /**
719d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * Create from inside an XML document. Called on a parser positioned at a
729d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * tag in an XML document, tries to create a ColorStateList from that tag.
739d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     *
749d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @throws XmlPullParserException if the current tag is not <selector>
759d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * @return A new color state list for the current tag.
769d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     */
779d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    @NonNull
789d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    private static ColorStateList createFromXmlInner(@NonNull Resources r,
799d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
809d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            @Nullable Resources.Theme theme)
819d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            throws XmlPullParserException, IOException {
829d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        final String name = parser.getName();
839d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        if (!name.equals("selector")) {
849d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            throw new XmlPullParserException(
859d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                    parser.getPositionDescription() + ": invalid color state list tag " + name);
869d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        }
879d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
889d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        return inflate(r, parser, attrs, theme);
899d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    }
909d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
919d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    /**
929d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     * Fill in this object based on the contents of an XML "selector" element.
939d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes     */
949d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
959d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
969d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            throws XmlPullParserException, IOException {
979d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        final int innerDepth = parser.getDepth() + 1;
989d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int depth;
999d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int type;
1009d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int defaultColor = DEFAULT_COLOR;
1019d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1029d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int[][] stateSpecList = new int[20][];
1039d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int[] colorList = new int[stateSpecList.length];
1049d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int listSize = 0;
1059d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1069d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1079d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
1089d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            if (type != XmlPullParser.START_TAG || depth > innerDepth
1099d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                    || !parser.getName().equals("item")) {
1109d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                continue;
1119d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            }
1129d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1139d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
1149d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
1159d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                    Color.MAGENTA);
1169d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1179d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            float alphaMod = 1.0f;
1189d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
1199d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod);
1209d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) {
1219d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod);
1229d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            }
1239d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1249d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            a.recycle();
1259d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1269d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            // Parse all unrecognized attributes as state specifiers.
1279d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            int j = 0;
1289d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            final int numAttrs = attrs.getAttributeCount();
1299d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            int[] stateSpec = new int[numAttrs];
1309d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            for (int i = 0; i < numAttrs; i++) {
1319d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                final int stateResId = attrs.getAttributeNameResource(i);
1329d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha
1339d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                        && stateResId != R.attr.alpha) {
1349d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                    // Unrecognized attribute, add to state set
1359d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                    stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
1369d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                            ? stateResId : -stateResId;
1379d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                }
1389d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            }
1399d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            stateSpec = StateSet.trimStateSet(stateSpec, j);
1409d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1419d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            // Apply alpha modulation. If we couldn't resolve the color or
1429d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            // alpha yet, the default values leave us enough information to
1439d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            // modulate again during applyTheme().
1449d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            final int color = modulateColorAlpha(baseColor, alphaMod);
1459d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            if (listSize == 0 || stateSpec.length == 0) {
1469d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                defaultColor = color;
1479d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            }
1489d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1499d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            colorList = GrowingArrayUtils.append(colorList, listSize, color);
1509d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
1519d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            listSize++;
1529d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        }
1539d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1549d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int[] colors = new int[listSize];
1559d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        int[][] stateSpecs = new int[listSize][];
1569d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        System.arraycopy(colorList, 0, colors, 0, listSize);
1579d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize);
1589d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1599d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        return new ColorStateList(stateSpecs, colors);
1609d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    }
1619d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1629d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
1639d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes            AttributeSet set, int[] attrs) {
1649d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        return theme == null ? res.obtainAttributes(set, attrs)
1659d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                : theme.obtainStyledAttributes(set, attrs, 0, 0);
1669d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    }
1679d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes
1689d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    private static int modulateColorAlpha(int color, float alphaMod) {
1699d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes        return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod));
1709d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes    }
1719d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes}
172