1 /*
2  * Copyright (C) 2014 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 androidx.appcompat.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
20 
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.graphics.PorterDuff;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.view.ActionMode;
27 import android.view.View;
28 import android.view.inputmethod.EditorInfo;
29 import android.view.inputmethod.InputConnection;
30 import android.widget.CheckedTextView;
31 import android.widget.TextView;
32 
33 import androidx.annotation.DrawableRes;
34 import androidx.annotation.RestrictTo;
35 import androidx.appcompat.R;
36 import androidx.appcompat.content.res.AppCompatResources;
37 import androidx.core.view.TintableBackgroundView;
38 import androidx.core.view.ViewCompat;
39 import androidx.core.widget.TextViewCompat;
40 import androidx.core.widget.TintableCheckedTextView;
41 import androidx.core.widget.TintableCompoundDrawablesView;
42 import androidx.resourceinspection.annotation.AppCompatShadowedAttributes;
43 
44 import org.jspecify.annotations.NonNull;
45 import org.jspecify.annotations.Nullable;
46 
47 /**
48  * A {@link CheckedTextView} which supports compatible features on older versions of the platform,
49  * including:
50  * <ul>
51  *     <li>Allows dynamic tint of its background via the background tint methods in
52  *     {@link androidx.core.view.ViewCompat}.</li>
53  *     <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
54  *     {@link R.attr#backgroundTintMode}.</li>
55  *     <li>Allows dynamic tint of its check mark via the check mark tint methods in
56  *     {@link androidx.core.widget.CheckedTextViewCompat}.</li>
57  *     <li>Allows setting of the check mark tint using {@link R.attr#checkMarkTint} and
58  *     {@link R.attr#checkMarkTintMode}.</li>
59  *     <li>Allows setting of the font family using {@link android.R.attr#fontFamily}</li>
60  * </ul>
61  *
62  * <p>This will automatically be used when you use {@link CheckedTextView} in your layouts
63  * and the top-level activity / dialog is provided by
64  * <a href="{@docRoot}topic/libraries/support-library/packages.html#v7-appcompat">appcompat</a>.
65  * You should only need to manually use this class when writing custom views.</p>
66  */
67 @AppCompatShadowedAttributes
68 public class AppCompatCheckedTextView extends CheckedTextView implements TintableCheckedTextView,
69         TintableBackgroundView, EmojiCompatConfigurationView, TintableCompoundDrawablesView {
70 
71     private final AppCompatCheckedTextViewHelper mCheckedHelper;
72     private final AppCompatBackgroundHelper mBackgroundTintHelper;
73     private final AppCompatTextHelper mTextHelper;
74     private @NonNull AppCompatEmojiTextHelper mAppCompatEmojiTextHelper;
75 
AppCompatCheckedTextView(@onNull Context context)76     public AppCompatCheckedTextView(@NonNull Context context) {
77         this(context, null);
78     }
79 
AppCompatCheckedTextView(@onNull Context context, @Nullable AttributeSet attrs)80     public AppCompatCheckedTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
81         this(context, attrs, R.attr.checkedTextViewStyle);
82     }
83 
AppCompatCheckedTextView( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)84     public AppCompatCheckedTextView(
85             @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
86         super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
87 
88         ThemeUtils.checkAppCompatTheme(this, getContext());
89 
90         mTextHelper = new AppCompatTextHelper(this);
91         mTextHelper.loadFromAttributes(attrs, defStyleAttr);
92         mTextHelper.applyCompoundDrawablesTints();
93 
94         mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
95         mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
96 
97         mCheckedHelper = new AppCompatCheckedTextViewHelper(this);
98         mCheckedHelper.loadFromAttributes(attrs, defStyleAttr);
99 
100         AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();
101         emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
102     }
103 
104     @Override
setCheckMarkDrawable(@ullable Drawable d)105     public void setCheckMarkDrawable(@Nullable Drawable d) {
106         super.setCheckMarkDrawable(d);
107         if (mCheckedHelper != null) {
108             mCheckedHelper.onSetCheckMarkDrawable();
109         }
110     }
111 
112     @Override
setCheckMarkDrawable(@rawableRes int resId)113     public void setCheckMarkDrawable(@DrawableRes int resId) {
114         setCheckMarkDrawable(AppCompatResources.getDrawable(getContext(), resId));
115     }
116 
117     /**
118      * This should be accessed from {@link androidx.core.widget.CheckedTextViewCompat}
119      *
120      */
121     @RestrictTo(LIBRARY_GROUP_PREFIX)
122     @Override
setSupportCheckMarkTintList(@ullable ColorStateList tint)123     public void setSupportCheckMarkTintList(@Nullable ColorStateList tint) {
124         if (mCheckedHelper != null) {
125             mCheckedHelper.setSupportCheckMarkTintList(tint);
126         }
127     }
128 
129     /**
130      * This should be accessed from {@link androidx.core.widget.CheckedTextViewCompat}
131      *
132      */
133     @RestrictTo(LIBRARY_GROUP_PREFIX)
134     @Override
getSupportCheckMarkTintList()135     public @Nullable ColorStateList getSupportCheckMarkTintList() {
136         return mCheckedHelper != null
137                 ? mCheckedHelper.getSupportCheckMarkTintList()
138                 : null;
139     }
140 
141     /**
142      * This should be accessed from {@link androidx.core.widget.CheckedTextViewCompat}
143      *
144      */
145     @RestrictTo(LIBRARY_GROUP_PREFIX)
146     @Override
setSupportCheckMarkTintMode(PorterDuff.@ullable Mode tintMode)147     public void setSupportCheckMarkTintMode(PorterDuff.@Nullable Mode tintMode) {
148         if (mCheckedHelper != null) {
149             mCheckedHelper.setSupportCheckMarkTintMode(tintMode);
150         }
151     }
152 
153     /**
154      * This should be accessed from {@link androidx.core.widget.CheckedTextViewCompat}
155      *
156      */
157     @RestrictTo(LIBRARY_GROUP_PREFIX)
158     @Override
getSupportCheckMarkTintMode()159     public PorterDuff.@Nullable Mode getSupportCheckMarkTintMode() {
160         return mCheckedHelper != null
161                 ? mCheckedHelper.getSupportCheckMarkTintMode()
162                 : null;
163     }
164 
165     /**
166      * This should be accessed via
167      * {@link ViewCompat#setBackgroundTintList(View, ColorStateList)}
168      */
169     @RestrictTo(LIBRARY_GROUP_PREFIX)
170     @Override
setSupportBackgroundTintList(@ullable ColorStateList tint)171     public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
172         if (mBackgroundTintHelper != null) {
173             mBackgroundTintHelper.setSupportBackgroundTintList(tint);
174         }
175     }
176 
177     /**
178      * This should be accessed via
179      * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
180      *
181      */
182     @RestrictTo(LIBRARY_GROUP_PREFIX)
183     @Override
getSupportBackgroundTintList()184     public @Nullable ColorStateList getSupportBackgroundTintList() {
185         return mBackgroundTintHelper != null
186                 ? mBackgroundTintHelper.getSupportBackgroundTintList() : null;
187     }
188 
189     /**
190      * This should be accessed via
191      * {@link ViewCompat#setBackgroundTintMode(View, PorterDuff.Mode)}
192      *
193      */
194     @RestrictTo(LIBRARY_GROUP_PREFIX)
195     @Override
setSupportBackgroundTintMode(PorterDuff.@ullable Mode tintMode)196     public void setSupportBackgroundTintMode(PorterDuff.@Nullable Mode tintMode) {
197         if (mBackgroundTintHelper != null) {
198             mBackgroundTintHelper.setSupportBackgroundTintMode(tintMode);
199         }
200     }
201 
202     /**
203      * This should be accessed via
204      * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
205      *
206      */
207     @RestrictTo(LIBRARY_GROUP_PREFIX)
208     @Override
getSupportBackgroundTintMode()209     public PorterDuff.@Nullable Mode getSupportBackgroundTintMode() {
210         return mBackgroundTintHelper != null
211                 ? mBackgroundTintHelper.getSupportBackgroundTintMode() : null;
212     }
213 
214     @Override
setBackgroundDrawable(@ullable Drawable background)215     public void setBackgroundDrawable(@Nullable Drawable background) {
216         super.setBackgroundDrawable(background);
217         if (mBackgroundTintHelper != null) {
218             mBackgroundTintHelper.onSetBackgroundDrawable(background);
219         }
220     }
221 
222     @Override
setBackgroundResource(@rawableRes int resId)223     public void setBackgroundResource(@DrawableRes int resId) {
224         super.setBackgroundResource(resId);
225         if (mBackgroundTintHelper != null) {
226             mBackgroundTintHelper.onSetBackgroundResource(resId);
227         }
228     }
229 
230     @Override
setTextAppearance(@onNull Context context, int resId)231     public void setTextAppearance(@NonNull Context context, int resId) {
232         super.setTextAppearance(context, resId);
233         if (mTextHelper != null) {
234             mTextHelper.onSetTextAppearance(context, resId);
235         }
236     }
237 
238     @Override
drawableStateChanged()239     protected void drawableStateChanged() {
240         super.drawableStateChanged();
241         if (mTextHelper != null) {
242             mTextHelper.applyCompoundDrawablesTints();
243         }
244         if (mBackgroundTintHelper != null) {
245             mBackgroundTintHelper.applySupportBackgroundTint();
246         }
247         if (mCheckedHelper != null) {
248             mCheckedHelper.applyCheckMarkTint();
249         }
250     }
251 
252     @Override
onCreateInputConnection(@onNull EditorInfo outAttrs)253     public @Nullable InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) {
254         return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
255                 outAttrs, this);
256     }
257 
258     /**
259      * See
260      * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
261      */
262     @Override
setCustomSelectionActionModeCallback( ActionMode.@ullable Callback actionModeCallback)263     public void setCustomSelectionActionModeCallback(
264             ActionMode.@Nullable Callback actionModeCallback) {
265         super.setCustomSelectionActionModeCallback(
266                 TextViewCompat.wrapCustomSelectionActionModeCallback(this, actionModeCallback));
267     }
268 
269     @Override
getCustomSelectionActionModeCallback()270     public ActionMode.@Nullable Callback getCustomSelectionActionModeCallback() {
271         return TextViewCompat.unwrapCustomSelectionActionModeCallback(
272                 super.getCustomSelectionActionModeCallback());
273     }
274 
275     /**
276      * This may be called from super constructors.
277      */
getEmojiTextViewHelper()278     private @NonNull AppCompatEmojiTextHelper getEmojiTextViewHelper() {
279         //noinspection ConstantConditions
280         if (mAppCompatEmojiTextHelper == null) {
281             mAppCompatEmojiTextHelper = new AppCompatEmojiTextHelper(this);
282         }
283         return mAppCompatEmojiTextHelper;
284     }
285 
286     @Override
setAllCaps(boolean allCaps)287     public void setAllCaps(boolean allCaps) {
288         super.setAllCaps(allCaps);
289         getEmojiTextViewHelper().setAllCaps(allCaps);
290     }
291 
292 
293     @Override
setEmojiCompatEnabled(boolean enabled)294     public void setEmojiCompatEnabled(boolean enabled) {
295         getEmojiTextViewHelper().setEnabled(enabled);
296     }
297 
298     @Override
isEmojiCompatEnabled()299     public boolean isEmojiCompatEnabled() {
300         return getEmojiTextViewHelper().isEnabled();
301     }
302 
303     @Override
setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)304     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
305             @Nullable Drawable right, @Nullable Drawable bottom) {
306         super.setCompoundDrawables(left, top, right, bottom);
307         if (mTextHelper != null) {
308             mTextHelper.onSetCompoundDrawables();
309         }
310     }
311 
312     @Override
setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)313     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
314             @Nullable Drawable end, @Nullable Drawable bottom) {
315         super.setCompoundDrawablesRelative(start, top, end, bottom);
316         if (mTextHelper != null) {
317             mTextHelper.onSetCompoundDrawables();
318         }
319     }
320 
321     /**
322      * This should be accessed via
323      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintList(TextView)}
324      *
325      * @return the tint applied to the compound drawables
326      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
327      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
328      *
329      */
330     @Override
331     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintList()332     public @Nullable ColorStateList getSupportCompoundDrawablesTintList() {
333         return mTextHelper.getCompoundDrawableTintList();
334     }
335 
336     /**
337      * This should be accessed via {@link
338      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintList(TextView, ColorStateList)}
339      *
340      * Applies a tint to the compound drawables. Does not modify the current tint mode, which is
341      * {@link PorterDuff.Mode#SRC_IN} by default.
342      * <p>
343      * Subsequent calls to {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} and
344      * related methods will automatically mutate the drawables and apply the specified tint and tint
345      * mode using {@link Drawable#setTintList(ColorStateList)}.
346      *
347      * @param tintList the tint to apply, may be {@code null} to clear tint
348      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTint
349      * @see #getSupportCompoundDrawablesTintList()
350      *
351      */
352     @Override
353     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintList(@ullable ColorStateList tintList)354     public void setSupportCompoundDrawablesTintList(@Nullable ColorStateList tintList) {
355         mTextHelper.setCompoundDrawableTintList(tintList);
356         mTextHelper.applyCompoundDrawablesTints();
357     }
358 
359     /**
360      * This should be accessed via
361      * {@link androidx.core.widget.TextViewCompat#getCompoundDrawableTintMode(TextView)}
362      *
363      * Returns the blending mode used to apply the tint to the compound drawables, if specified.
364      *
365      * @return the blending mode used to apply the tint to the compound drawables
366      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
367      * @see #setSupportCompoundDrawablesTintMode(PorterDuff.Mode)
368      *
369      */
370     @Override
371     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSupportCompoundDrawablesTintMode()372     public PorterDuff.@Nullable Mode getSupportCompoundDrawablesTintMode() {
373         return mTextHelper.getCompoundDrawableTintMode();
374     }
375 
376     /**
377      * This should be accessed via {@link
378      * androidx.core.widget.TextViewCompat#setCompoundDrawableTintMode(TextView, PorterDuff.Mode)}
379      *
380      * Specifies the blending mode used to apply the tint specified by
381      * {@link #setSupportCompoundDrawablesTintList(ColorStateList)} to the compound drawables. The
382      * default mode is {@link PorterDuff.Mode#SRC_IN}.
383      *
384      * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
385      * @attr ref androidx.appcompat.R.styleable#AppCompatTextView_drawableTintMode
386      * @see #setSupportCompoundDrawablesTintList(ColorStateList)
387      *
388      */
389     @Override
390     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSupportCompoundDrawablesTintMode(PorterDuff.@ullable Mode tintMode)391     public void setSupportCompoundDrawablesTintMode(PorterDuff.@Nullable Mode tintMode) {
392         mTextHelper.setCompoundDrawableTintMode(tintMode);
393         mTextHelper.applyCompoundDrawablesTints();
394     }
395 }
396