1091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes/*
2091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Copyright (C) 2015 The Android Open Source Project
3091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes *
4091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * you may not use this file except in compliance with the License.
6091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * You may obtain a copy of the License at
7091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes *
8091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes *
10091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Unless required by applicable law or agreed to in writing, software
11091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * See the License for the specific language governing permissions and
14091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * limitations under the License.
15091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes */
16091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
17091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banespackage android.support.v7.widget;
18091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
19091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.content.Context;
2089a45a915143f97174f5b6d45273c80503a7efcdChris Banesimport android.content.res.ColorStateList;
21f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banesimport android.graphics.drawable.Drawable;
22091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.os.Build;
23091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.support.v7.appcompat.R;
2466698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport android.support.v7.text.AllCapsTransformationMethod;
2540b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banesimport android.text.method.PasswordTransformationMethod;
26091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.util.AttributeSet;
27091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.widget.TextView;
28091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
29091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesclass AppCompatTextHelper {
30091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
31f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    static AppCompatTextHelper create(TextView textView) {
32f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        if (Build.VERSION.SDK_INT >= 17) {
33f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            return new AppCompatTextHelperV17(textView);
34f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
35f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        return new AppCompatTextHelper(textView);
36f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    }
37f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes
38f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    final TextView mView;
39f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes
40f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    private TintInfo mDrawableLeftTint;
41f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    private TintInfo mDrawableTopTint;
42f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    private TintInfo mDrawableRightTint;
43f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    private TintInfo mDrawableBottomTint;
44091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
45091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    AppCompatTextHelper(TextView view) {
46091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        mView = view;
47091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    }
48091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
49091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
50f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        final Context context = mView.getContext();
517e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
52091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
53091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        // First read the TextAppearance style id
5419509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
5531a373d741c19f383578224434372a8f7a5b7a83Chris Banes                R.styleable.AppCompatTextHelper, defStyleAttr, 0);
5631a373d741c19f383578224434372a8f7a5b7a83Chris Banes        final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1);
57f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        // Now read the compound drawable and grab any tints
5848dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {
5931a373d741c19f383578224434372a8f7a5b7a83Chris Banes            mDrawableLeftTint = createTintInfo(context, drawableManager,
6031a373d741c19f383578224434372a8f7a5b7a83Chris Banes                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0));
61f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
6248dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) {
6331a373d741c19f383578224434372a8f7a5b7a83Chris Banes            mDrawableTopTint = createTintInfo(context, drawableManager,
6431a373d741c19f383578224434372a8f7a5b7a83Chris Banes                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0));
65f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
6648dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) {
6731a373d741c19f383578224434372a8f7a5b7a83Chris Banes            mDrawableRightTint = createTintInfo(context, drawableManager,
6831a373d741c19f383578224434372a8f7a5b7a83Chris Banes                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0));
69f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
7048dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes        if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) {
7131a373d741c19f383578224434372a8f7a5b7a83Chris Banes            mDrawableBottomTint = createTintInfo(context, drawableManager,
7231a373d741c19f383578224434372a8f7a5b7a83Chris Banes                    a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0));
73f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
74091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        a.recycle();
75091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
7619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        // PasswordTransformationMethod wipes out all other TransformationMethod instances
7719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        // in TextView's constructor, so we should only set a new transformation method
7819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        // if we don't have a PasswordTransformationMethod currently...
7919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        final boolean hasPwdTm =
8019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                mView.getTransformationMethod() instanceof PasswordTransformationMethod;
8119509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        boolean allCaps = false;
82464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes        boolean allCapsSet = false;
839eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes        ColorStateList textColor = null;
84286a5138bee6a1597c093e84e52b510c439302d7Chris Banes        ColorStateList textColorHint = null;
8519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes
8619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        // First check TextAppearance's textAllCaps value
8719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        if (ap != -1) {
8819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
8919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
905a044fddba6ba7295599dddc2ff397925823efa8Chris Banes                allCapsSet = true;
9119509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
92091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes            }
93286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            if (Build.VERSION.SDK_INT < 23) {
949eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes                // If we're running on < API 23, the text color may contain theme references
959eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes                // so let's re-set using our own inflater
96286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
97286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                    textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
98286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                }
99286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {
100286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                    textColorHint = a.getColorStateList(
101286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                            R.styleable.TextAppearance_android_textColorHint);
102286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                }
10340b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes            }
10440b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes            a.recycle();
10519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        }
10640b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes
10719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        // Now read the style's values
10819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
10919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                defStyleAttr, 0);
11019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
111464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes            allCapsSet = true;
11219509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
11319509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        }
114286a5138bee6a1597c093e84e52b510c439302d7Chris Banes        if (Build.VERSION.SDK_INT < 23) {
1159eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes            // If we're running on < API 23, the text color may contain theme references
1169eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes            // so let's re-set using our own inflater
117286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
118286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
119286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            }
120286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {
121286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                textColorHint = a.getColorStateList(
122286a5138bee6a1597c093e84e52b510c439302d7Chris Banes                        R.styleable.TextAppearance_android_textColorHint);
123286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            }
124091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        }
12519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        a.recycle();
12619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes
1279eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes        if (textColor != null) {
1289eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes            mView.setTextColor(textColor);
1299eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes        }
130286a5138bee6a1597c093e84e52b510c439302d7Chris Banes        if (textColorHint != null) {
131286a5138bee6a1597c093e84e52b510c439302d7Chris Banes            mView.setHintTextColor(textColorHint);
132286a5138bee6a1597c093e84e52b510c439302d7Chris Banes        }
133464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes        if (!hasPwdTm && allCapsSet) {
134464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes            setAllCaps(allCaps);
13519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        }
136091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    }
137091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
138091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    void onSetTextAppearance(Context context, int resId) {
13919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
14019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                resId, R.styleable.TextAppearance);
14139e275d4c81cd3470f5ed78fa147d214f70ee580Chris Banes        if (a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
1422a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes            // This breaks away slightly from the logic in TextView.setTextAppearance that serves
1432a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes            // as an "overlay" on the current state of the TextView. Since android:textAllCaps
1442a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes            // may have been set to true in this text appearance, we need to make sure that
1452a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes            // app:textAllCaps has the chance to override it
14639e275d4c81cd3470f5ed78fa147d214f70ee580Chris Banes            setAllCaps(a.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
147091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        }
1489eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes        if (Build.VERSION.SDK_INT < 23
1499eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes                && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
1509eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes            // If we're running on < API 23, the text color may contain theme references
1519eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes            // so let's re-set using our own inflater
15219509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            final ColorStateList textColor
15319509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                    = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
15419509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            if (textColor != null) {
15519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes                mView.setTextColor(textColor);
15619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes            }
15719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        }
15819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes        a.recycle();
159091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    }
160091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes
161091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    void setAllCaps(boolean allCaps) {
162091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes        mView.setTransformationMethod(allCaps
163091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes                ? new AllCapsTransformationMethod(mView.getContext())
164091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes                : null);
165091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes    }
166f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes
167f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    void applyCompoundDrawablesTints() {
168f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        if (mDrawableLeftTint != null || mDrawableTopTint != null ||
169f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes                mDrawableRightTint != null || mDrawableBottomTint != null) {
170f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            final Drawable[] compoundDrawables = mView.getCompoundDrawables();
171f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            applyCompoundDrawableTint(compoundDrawables[0], mDrawableLeftTint);
172f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            applyCompoundDrawableTint(compoundDrawables[1], mDrawableTopTint);
173f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            applyCompoundDrawableTint(compoundDrawables[2], mDrawableRightTint);
174f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes            applyCompoundDrawableTint(compoundDrawables[3], mDrawableBottomTint);
175f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
176f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    }
177f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes
178f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) {
179f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        if (drawable != null && info != null) {
1807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            AppCompatDrawableManager.tintDrawable(drawable, info, mView.getDrawableState());
181f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes        }
182f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    }
18389a45a915143f97174f5b6d45273c80503a7efcdChris Banes
184a466cf9e5e70e638097a1753dd454a98fed05068Chris Banes    protected static TintInfo createTintInfo(Context context,
18589a45a915143f97174f5b6d45273c80503a7efcdChris Banes            AppCompatDrawableManager drawableManager, int drawableId) {
18689a45a915143f97174f5b6d45273c80503a7efcdChris Banes        final ColorStateList tintList = drawableManager.getTintList(context, drawableId);
18789a45a915143f97174f5b6d45273c80503a7efcdChris Banes        if (tintList != null) {
18889a45a915143f97174f5b6d45273c80503a7efcdChris Banes            final TintInfo tintInfo = new TintInfo();
18989a45a915143f97174f5b6d45273c80503a7efcdChris Banes            tintInfo.mHasTintList = true;
19089a45a915143f97174f5b6d45273c80503a7efcdChris Banes            tintInfo.mTintList = tintList;
191e23a1bdaf16bf535a1a6bab469e3d63bb6d73712Chris Banes            return tintInfo;
19289a45a915143f97174f5b6d45273c80503a7efcdChris Banes        }
19389a45a915143f97174f5b6d45273c80503a7efcdChris Banes        return null;
19489a45a915143f97174f5b6d45273c80503a7efcdChris Banes    }
195091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes}
196