1414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes/* 2414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * Copyright (C) 2014 The Android Open Source Project 3414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * 4414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * you may not use this file except in compliance with the License. 6414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * You may obtain a copy of the License at 7414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * 8414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * http://www.apache.org/licenses/LICENSE-2.0 9414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * 10414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * Unless required by applicable law or agreed to in writing, software 11414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * See the License for the specific language governing permissions and 14414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * limitations under the License. 15414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes */ 16414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget; 18414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 19aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.PorterDuff; 20414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport android.graphics.Rect; 21414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport android.graphics.drawable.Drawable; 22aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.drawable.DrawableContainer; 23aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.drawable.GradientDrawable; 24aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.drawable.InsetDrawable; 25aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.drawable.LayerDrawable; 26a7331ff81580eafb62c67a083e53ca82658c556eChris Banesimport android.graphics.drawable.ScaleDrawable; 27aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.graphics.drawable.StateListDrawable; 28414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport android.os.Build; 29aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banesimport android.support.annotation.NonNull; 30c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo; 317e82b99953680915596eaf0eb35927388e574ca8Chris Banesimport android.support.v4.graphics.drawable.DrawableCompat; 32414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport android.util.Log; 33414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 34414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport java.lang.reflect.Field; 35414f52397a88d52a783a31d4c098bc3bec632b8dChris Banesimport java.lang.reflect.Method; 36414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 37c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport static android.support.annotation.RestrictTo.Scope.GROUP_ID; 38c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette 390f6a807ceaa96930e57e55826ddaae5eab692aa8Kirill Grouchnikov/** @hide */ 40c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette@RestrictTo(GROUP_ID) 410f6a807ceaa96930e57e55826ddaae5eab692aa8Kirill Grouchnikovpublic class DrawableUtils { 42414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 43414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes private static final String TAG = "DrawableUtils"; 44414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 45414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes public static final Rect INSETS_NONE = new Rect(); 46414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes private static Class<?> sInsetsClazz; 47414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 48aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes private static final String VECTOR_DRAWABLE_CLAZZ_NAME 49aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes = "android.graphics.drawable.VectorDrawable"; 50aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes 51414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes static { 52414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes if (Build.VERSION.SDK_INT >= 18) { 53414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes try { 54414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes sInsetsClazz = Class.forName("android.graphics.Insets"); 55414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } catch (ClassNotFoundException e) { 56414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes // Oh well... 57414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 58414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 59414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 60414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 61414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes private DrawableUtils() {} 62414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 63414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes /** 64414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * Allows us to get the optical insets for a {@link Drawable}. Since this is hidden we need to 65414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes * use reflection. Since the {@code Insets} class is hidden also, we return a Rect instead. 66414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes */ 67414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes public static Rect getOpticalBounds(Drawable drawable) { 68414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes if (sInsetsClazz != null) { 69414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes try { 707e82b99953680915596eaf0eb35927388e574ca8Chris Banes // If the Drawable is wrapped, we need to manually unwrap it and process 717e82b99953680915596eaf0eb35927388e574ca8Chris Banes // the wrapped drawable. 727e82b99953680915596eaf0eb35927388e574ca8Chris Banes drawable = DrawableCompat.unwrap(drawable); 73414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 74414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes final Method getOpticalInsetsMethod = drawable.getClass() 75414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes .getMethod("getOpticalInsets"); 76414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes final Object insets = getOpticalInsetsMethod.invoke(drawable); 77414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 78414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes if (insets != null) { 79414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes // If the drawable has some optical insets, let's copy them into a Rect 80414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes final Rect result = new Rect(); 81414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 82414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes for (Field field : sInsetsClazz.getFields()) { 83414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes switch (field.getName()) { 84414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes case "left": 85414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes result.left = field.getInt(insets); 86414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes break; 87414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes case "top": 88414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes result.top = field.getInt(insets); 89414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes break; 90414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes case "right": 91414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes result.right = field.getInt(insets); 92414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes break; 93414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes case "bottom": 94414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes result.bottom = field.getInt(insets); 95414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes break; 96414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 97414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 98414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes return result; 99414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 100414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } catch (Exception e) { 101414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes // Eugh, we hit some kind of reflection issue... 102414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes Log.e(TAG, "Couldn't obtain the optical insets. Ignoring."); 103414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 104414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 105414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 106414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes // If we reach here, either we're running on a device pre-v18, the Drawable didn't have 107414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes // any optical insets, or a reflection issue, so we'll just return an empty rect 108414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes return INSETS_NONE; 109414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes } 110414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes 111aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes /** 112aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * Attempt the fix any issues in the given drawable, usually caused by platform bugs in the 113aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * implementation. This method should be call after retrieval from 114aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * {@link android.content.res.Resources} or a {@link android.content.res.TypedArray}. 115aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes */ 116aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes static void fixDrawable(@NonNull final Drawable drawable) { 117aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes if (Build.VERSION.SDK_INT == 21 118aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes && VECTOR_DRAWABLE_CLAZZ_NAME.equals(drawable.getClass().getName())) { 119aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes fixVectorDrawableTinting(drawable); 120aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 121aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 122aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes 123aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes /** 124aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * Some drawable implementations have problems with mutation. This method returns false if 125aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * there is a known issue in the given drawable's implementation. 126aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes */ 1270f6a807ceaa96930e57e55826ddaae5eab692aa8Kirill Grouchnikov public static boolean canSafelyMutateDrawable(@NonNull Drawable drawable) { 12864dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov if (Build.VERSION.SDK_INT < 15 && drawable instanceof InsetDrawable) { 1294237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes return false; 1304237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes } else if (Build.VERSION.SDK_INT < 15 && drawable instanceof GradientDrawable) { 131aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // GradientDrawable has a bug pre-ICS which results in mutate() resulting 132aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // in loss of color 1334237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes return false; 1344237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes } else if (Build.VERSION.SDK_INT < 17 && drawable instanceof LayerDrawable) { 1354237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes return false; 1364237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes } 1374237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes 1384237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes if (drawable instanceof DrawableContainer) { 139aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // If we have a DrawableContainer, let's traverse it's child array 140aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes final Drawable.ConstantState state = drawable.getConstantState(); 141aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes if (state instanceof DrawableContainer.DrawableContainerState) { 142aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes final DrawableContainer.DrawableContainerState containerState = 143aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes (DrawableContainer.DrawableContainerState) state; 1444237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes for (final Drawable child : containerState.getChildren()) { 145aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes if (!canSafelyMutateDrawable(child)) { 146aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes return false; 147aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 148aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 149aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 150547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes } else if (drawable instanceof android.support.v4.graphics.drawable.DrawableWrapper) { 151547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes return canSafelyMutateDrawable( 152547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes ((android.support.v4.graphics.drawable.DrawableWrapper) drawable) 153547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes .getWrappedDrawable()); 154547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes } else if (drawable instanceof android.support.v7.graphics.drawable.DrawableWrapper) { 155547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes return canSafelyMutateDrawable( 156547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes ((android.support.v7.graphics.drawable.DrawableWrapper) drawable) 157547c54f394ad16eb8d7ded400e2d2cf6e7256bb8Chris Banes .getWrappedDrawable()); 158a7331ff81580eafb62c67a083e53ca82658c556eChris Banes } else if (drawable instanceof ScaleDrawable) { 159a7331ff81580eafb62c67a083e53ca82658c556eChris Banes return canSafelyMutateDrawable(((ScaleDrawable) drawable).getDrawable()); 160aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 1614237288e940e6c9c0ec8719dffd6b63fedd9056aChris Banes 162aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes return true; 163aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 164aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes 165aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes /** 166aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * VectorDrawable has an issue on API 21 where it sometimes doesn't create its tint filter. 167aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes * Fixed by toggling it's state to force a filter creation. 168aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes */ 169aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes private static void fixVectorDrawableTinting(final Drawable drawable) { 170aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes final int[] originalState = drawable.getState(); 171aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes if (originalState == null || originalState.length == 0) { 172aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // The drawable doesn't have a state, so set it to be checked 173aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes drawable.setState(ThemeUtils.CHECKED_STATE_SET); 174aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } else { 175aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // Else the drawable does have a state, so clear it 176aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes drawable.setState(ThemeUtils.EMPTY_STATE_SET); 177aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 178aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes // Now set the original state 179aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes drawable.setState(originalState); 180aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 181aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes 182aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { 183aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes switch (value) { 184aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 3: return PorterDuff.Mode.SRC_OVER; 185aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 5: return PorterDuff.Mode.SRC_IN; 186aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 9: return PorterDuff.Mode.SRC_ATOP; 187aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 14: return PorterDuff.Mode.MULTIPLY; 188aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 15: return PorterDuff.Mode.SCREEN; 189aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes case 16: return Build.VERSION.SDK_INT >= 11 190aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes ? PorterDuff.Mode.valueOf("ADD") 191aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes : defaultMode; 192aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes default: return defaultMode; 193aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 194aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes } 195aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes 196414f52397a88d52a783a31d4c098bc3bec632b8dChris Banes} 197