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.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.graphics.Typeface;
26 import android.graphics.drawable.Drawable;
27 import android.os.Build;
28 import android.util.AttributeSet;
29 import android.util.TypedValue;
30 
31 import androidx.annotation.RequiresApi;
32 import androidx.annotation.RestrictTo;
33 import androidx.annotation.StyleableRes;
34 import androidx.appcompat.content.res.AppCompatResources;
35 import androidx.core.content.res.ResourcesCompat;
36 
37 import org.jspecify.annotations.Nullable;
38 
39 /**
40  * A class that wraps a {@link TypedArray} and provides the same public API
41  * surface. The purpose of this class is so that we can intercept calls to new APIs.
42  *
43  */
44 @RestrictTo(LIBRARY_GROUP_PREFIX)
45 public class TintTypedArray {
46 
47     private final Context mContext;
48     private final TypedArray mWrapped;
49 
50     private TypedValue mTypedValue;
51 
obtainStyledAttributes(Context context, AttributeSet set, int[] attrs)52     public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
53             int[] attrs) {
54         return new TintTypedArray(context, context.obtainStyledAttributes(set, attrs));
55     }
56 
obtainStyledAttributes(Context context, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)57     public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
58             int[] attrs, int defStyleAttr, int defStyleRes) {
59         return new TintTypedArray(context,
60                 context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes));
61     }
62 
obtainStyledAttributes(Context context, int resid, int[] attrs)63     public static TintTypedArray obtainStyledAttributes(Context context, int resid, int[] attrs) {
64         return new TintTypedArray(context, context.obtainStyledAttributes(resid, attrs));
65     }
66 
TintTypedArray(Context context, TypedArray array)67     private TintTypedArray(Context context, TypedArray array) {
68         mContext = context;
69         mWrapped = array;
70     }
71 
72     /**
73      * Beware, you very likely do not intend to this method. Proceed with caution.
74      */
getWrappedTypeArray()75     public TypedArray getWrappedTypeArray() {
76         return mWrapped;
77     }
78 
getDrawable(int index)79     public Drawable getDrawable(int index) {
80         if (mWrapped.hasValue(index)) {
81             final int resourceId = mWrapped.getResourceId(index, 0);
82             if (resourceId != 0) {
83                 return AppCompatResources.getDrawable(mContext, resourceId);
84             }
85         }
86         return mWrapped.getDrawable(index);
87     }
88 
getDrawableIfKnown(int index)89     public Drawable getDrawableIfKnown(int index) {
90         if (mWrapped.hasValue(index)) {
91             final int resourceId = mWrapped.getResourceId(index, 0);
92             if (resourceId != 0) {
93                 return AppCompatDrawableManager.get().getDrawable(mContext, resourceId, true);
94             }
95         }
96         return null;
97     }
98 
99     /**
100      * Retrieve the Typeface for the attribute at <var>index</var>.
101      * <p>
102      * This method will throw an exception if the attribute is defined but is
103      * not a font.
104      *
105      * @param index Index of attribute to retrieve.
106      * @param style A style value used for selecting best match font from the list of family. Note
107      * that this value will be ignored if the platform supports font family (API 24 or later).
108      * @param fontCallback A callback to receive async fetching of this font. If async loading is
109      *                     specified in XML, this callback will be triggered.
110      *
111      * @return Typeface for the attribute, or {@code null} if not defined.
112      * @throws RuntimeException if the TypedArray has already been recycled.
113      * @throws UnsupportedOperationException if the attribute is defined but is
114      *         not a font resource.
115      */
getFont(@tyleableRes int index, int style, ResourcesCompat.@Nullable FontCallback fontCallback)116     public @Nullable Typeface getFont(@StyleableRes int index, int style,
117             ResourcesCompat.@Nullable FontCallback fontCallback) {
118         final int resourceId = mWrapped.getResourceId(index, 0);
119         if (resourceId == 0) {
120             return null;
121         }
122         if (mTypedValue == null) {
123             mTypedValue = new TypedValue();
124         }
125         return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
126     }
127 
length()128     public int length() {
129         return mWrapped.length();
130     }
131 
getIndexCount()132     public int getIndexCount() {
133         return mWrapped.getIndexCount();
134     }
135 
getIndex(int at)136     public int getIndex(int at) {
137         return mWrapped.getIndex(at);
138     }
139 
getResources()140     public Resources getResources() {
141         return mWrapped.getResources();
142     }
143 
getText(int index)144     public CharSequence getText(int index) {
145         return mWrapped.getText(index);
146     }
147 
getString(int index)148     public String getString(int index) {
149         return mWrapped.getString(index);
150     }
151 
getNonResourceString(int index)152     public String getNonResourceString(int index) {
153         return mWrapped.getNonResourceString(index);
154     }
155 
getBoolean(int index, boolean defValue)156     public boolean getBoolean(int index, boolean defValue) {
157         return mWrapped.getBoolean(index, defValue);
158     }
159 
getInt(int index, int defValue)160     public int getInt(int index, int defValue) {
161         return mWrapped.getInt(index, defValue);
162     }
163 
getFloat(int index, float defValue)164     public float getFloat(int index, float defValue) {
165         return mWrapped.getFloat(index, defValue);
166     }
167 
getColor(int index, int defValue)168     public int getColor(int index, int defValue) {
169         return mWrapped.getColor(index, defValue);
170     }
171 
getColorStateList(int index)172     public ColorStateList getColorStateList(int index) {
173         if (mWrapped.hasValue(index)) {
174             final int resourceId = mWrapped.getResourceId(index, 0);
175             if (resourceId != 0) {
176                 final ColorStateList value =
177                         AppCompatResources.getColorStateList(mContext, resourceId);
178                 if (value != null) {
179                     return value;
180                 }
181             }
182         }
183         return mWrapped.getColorStateList(index);
184     }
185 
getInteger(int index, int defValue)186     public int getInteger(int index, int defValue) {
187         return mWrapped.getInteger(index, defValue);
188     }
189 
getDimension(int index, float defValue)190     public float getDimension(int index, float defValue) {
191         return mWrapped.getDimension(index, defValue);
192     }
193 
getDimensionPixelOffset(int index, int defValue)194     public int getDimensionPixelOffset(int index, int defValue) {
195         return mWrapped.getDimensionPixelOffset(index, defValue);
196     }
197 
getDimensionPixelSize(int index, int defValue)198     public int getDimensionPixelSize(int index, int defValue) {
199         return mWrapped.getDimensionPixelSize(index, defValue);
200     }
201 
getLayoutDimension(int index, String name)202     public int getLayoutDimension(int index, String name) {
203         return mWrapped.getLayoutDimension(index, name);
204     }
205 
getLayoutDimension(int index, int defValue)206     public int getLayoutDimension(int index, int defValue) {
207         return mWrapped.getLayoutDimension(index, defValue);
208     }
209 
getFraction(int index, int base, int pbase, float defValue)210     public float getFraction(int index, int base, int pbase, float defValue) {
211         return mWrapped.getFraction(index, base, pbase, defValue);
212     }
213 
getResourceId(int index, int defValue)214     public int getResourceId(int index, int defValue) {
215         return mWrapped.getResourceId(index, defValue);
216     }
217 
getTextArray(int index)218     public CharSequence[] getTextArray(int index) {
219         return mWrapped.getTextArray(index);
220     }
221 
getValue(int index, TypedValue outValue)222     public boolean getValue(int index, TypedValue outValue) {
223         return mWrapped.getValue(index, outValue);
224     }
225 
getType(int index)226     public int getType(int index) {
227         if (Build.VERSION.SDK_INT >= 21) {
228             return Api21Impl.getType(mWrapped, index);
229         } else {
230             if (mTypedValue == null) {
231                 mTypedValue = new TypedValue();
232             }
233             mWrapped.getValue(index, mTypedValue);
234             return mTypedValue.type;
235         }
236     }
237 
hasValue(int index)238     public boolean hasValue(int index) {
239         return mWrapped.hasValue(index);
240     }
241 
peekValue(int index)242     public TypedValue peekValue(int index) {
243         return mWrapped.peekValue(index);
244     }
245 
getPositionDescription()246     public String getPositionDescription() {
247         return mWrapped.getPositionDescription();
248     }
249 
recycle()250     public void recycle() {
251         mWrapped.recycle();
252     }
253 
254     @RequiresApi(21)
getChangingConfigurations()255     public int getChangingConfigurations() {
256         return Api21Impl.getChangingConfigurations(mWrapped);
257     }
258 
259     @RequiresApi(21)
260     static class Api21Impl {
Api21Impl()261         private Api21Impl() {
262             // This class is not instantiable.
263         }
264 
getType(TypedArray typedArray, int index)265         static int getType(TypedArray typedArray, int index) {
266             return typedArray.getType(index);
267         }
268 
getChangingConfigurations(TypedArray typedArray)269         static int getChangingConfigurations(TypedArray typedArray) {
270             return typedArray.getChangingConfigurations();
271         }
272     }
273 }
274