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