1 /*
2  * Copyright (C) 2013 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 android.support.v4.graphics.drawable;
18 
19 import android.content.res.ColorStateList;
20 import android.graphics.PorterDuff;
21 import android.graphics.drawable.Drawable;
22 import android.support.v4.view.ViewCompat;
23 
24 /**
25  * Helper for accessing features in {@link android.graphics.drawable.Drawable}
26  * introduced after API level 4 in a backwards compatible fashion.
27  */
28 public class DrawableCompat {
29     /**
30      * Interface for the full API.
31      */
32     interface DrawableImpl {
jumpToCurrentState(Drawable drawable)33         void jumpToCurrentState(Drawable drawable);
setAutoMirrored(Drawable drawable, boolean mirrored)34         void setAutoMirrored(Drawable drawable, boolean mirrored);
isAutoMirrored(Drawable drawable)35         boolean isAutoMirrored(Drawable drawable);
setHotspot(Drawable drawable, float x, float y)36         void setHotspot(Drawable drawable, float x, float y);
setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)37         void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom);
setTint(Drawable drawable, int tint)38         void setTint(Drawable drawable, int tint);
setTintList(Drawable drawable, ColorStateList tint)39         void setTintList(Drawable drawable, ColorStateList tint);
setTintMode(Drawable drawable, PorterDuff.Mode tintMode)40         void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
wrap(Drawable drawable)41         Drawable wrap(Drawable drawable);
setLayoutDirection(Drawable drawable, int layoutDirection)42         void setLayoutDirection(Drawable drawable, int layoutDirection);
getLayoutDirection(Drawable drawable)43         int getLayoutDirection(Drawable drawable);
44     }
45 
46     /**
47      * Interface implementation that doesn't use anything about v4 APIs.
48      */
49     static class BaseDrawableImpl implements DrawableImpl {
50         @Override
jumpToCurrentState(Drawable drawable)51         public void jumpToCurrentState(Drawable drawable) {
52         }
53 
54         @Override
setAutoMirrored(Drawable drawable, boolean mirrored)55         public void setAutoMirrored(Drawable drawable, boolean mirrored) {
56         }
57 
58         @Override
isAutoMirrored(Drawable drawable)59         public boolean isAutoMirrored(Drawable drawable) {
60             return false;
61         }
62 
63         @Override
setHotspot(Drawable drawable, float x, float y)64         public void setHotspot(Drawable drawable, float x, float y) {
65         }
66 
67         @Override
setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)68         public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
69         }
70 
71         @Override
setTint(Drawable drawable, int tint)72         public void setTint(Drawable drawable, int tint) {
73             DrawableCompatBase.setTint(drawable, tint);
74         }
75 
76         @Override
setTintList(Drawable drawable, ColorStateList tint)77         public void setTintList(Drawable drawable, ColorStateList tint) {
78             DrawableCompatBase.setTintList(drawable, tint);
79         }
80 
81         @Override
setTintMode(Drawable drawable, PorterDuff.Mode tintMode)82         public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
83             DrawableCompatBase.setTintMode(drawable, tintMode);
84         }
85 
86         @Override
wrap(Drawable drawable)87         public Drawable wrap(Drawable drawable) {
88             return DrawableCompatBase.wrapForTinting(drawable);
89         }
90 
91         @Override
setLayoutDirection(Drawable drawable, int layoutDirection)92         public void setLayoutDirection(Drawable drawable, int layoutDirection) {
93             // No op for API < 23
94         }
95 
96         @Override
getLayoutDirection(Drawable drawable)97         public int getLayoutDirection(Drawable drawable) {
98             return ViewCompat.LAYOUT_DIRECTION_LTR;
99         }
100     }
101 
102     /**
103      * Interface implementation for devices with at least v11 APIs.
104      */
105     static class HoneycombDrawableImpl extends BaseDrawableImpl {
106         @Override
jumpToCurrentState(Drawable drawable)107         public void jumpToCurrentState(Drawable drawable) {
108             DrawableCompatHoneycomb.jumpToCurrentState(drawable);
109         }
110 
111         @Override
wrap(Drawable drawable)112         public Drawable wrap(Drawable drawable) {
113             return DrawableCompatHoneycomb.wrapForTinting(drawable);
114         }
115     }
116 
117     static class JellybeanMr1DrawableImpl extends HoneycombDrawableImpl {
118         @Override
setLayoutDirection(Drawable drawable, int layoutDirection)119         public void setLayoutDirection(Drawable drawable, int layoutDirection) {
120             DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection);
121         }
122 
123         @Override
getLayoutDirection(Drawable drawable)124         public int getLayoutDirection(Drawable drawable) {
125             final int dir = DrawableCompatJellybeanMr1.getLayoutDirection(drawable);
126             return dir < 0 ? dir : ViewCompat.LAYOUT_DIRECTION_LTR;
127         }
128     }
129 
130     /**
131      * Interface implementation for devices with at least KitKat APIs.
132      */
133     static class KitKatDrawableImpl extends JellybeanMr1DrawableImpl {
134         @Override
setAutoMirrored(Drawable drawable, boolean mirrored)135         public void setAutoMirrored(Drawable drawable, boolean mirrored) {
136             DrawableCompatKitKat.setAutoMirrored(drawable, mirrored);
137         }
138 
139         @Override
isAutoMirrored(Drawable drawable)140         public boolean isAutoMirrored(Drawable drawable) {
141             return DrawableCompatKitKat.isAutoMirrored(drawable);
142         }
143 
144         @Override
wrap(Drawable drawable)145         public Drawable wrap(Drawable drawable) {
146             return DrawableCompatKitKat.wrapForTinting(drawable);
147         }
148     }
149 
150     /**
151      * Interface implementation for devices with at least L APIs.
152      */
153     static class LollipopDrawableImpl extends KitKatDrawableImpl {
154         @Override
setHotspot(Drawable drawable, float x, float y)155         public void setHotspot(Drawable drawable, float x, float y) {
156             DrawableCompatLollipop.setHotspot(drawable, x, y);
157         }
158 
159         @Override
setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)160         public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
161             DrawableCompatLollipop.setHotspotBounds(drawable, left, top, right, bottom);
162         }
163 
164         @Override
setTint(Drawable drawable, int tint)165         public void setTint(Drawable drawable, int tint) {
166             DrawableCompatLollipop.setTint(drawable, tint);
167         }
168 
169         @Override
setTintList(Drawable drawable, ColorStateList tint)170         public void setTintList(Drawable drawable, ColorStateList tint) {
171             DrawableCompatLollipop.setTintList(drawable, tint);
172         }
173 
174         @Override
setTintMode(Drawable drawable, PorterDuff.Mode tintMode)175         public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
176             DrawableCompatLollipop.setTintMode(drawable, tintMode);
177         }
178 
179         @Override
wrap(Drawable drawable)180         public Drawable wrap(Drawable drawable) {
181             return DrawableCompatLollipop.wrapForTinting(drawable);
182         }
183     }
184 
185     /**
186      * Interface implementation for devices with at least L APIs.
187      */
188     static class LollipopMr1DrawableImpl extends LollipopDrawableImpl {
189         @Override
wrap(Drawable drawable)190         public Drawable wrap(Drawable drawable) {
191             return DrawableCompatApi22.wrapForTinting(drawable);
192         }
193     }
194 
195     /**
196      * Interface implementation for devices with at least M APIs.
197      */
198     static class MDrawableImpl extends LollipopMr1DrawableImpl {
199         @Override
setLayoutDirection(Drawable drawable, int layoutDirection)200         public void setLayoutDirection(Drawable drawable, int layoutDirection) {
201             DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection);
202         }
203 
204         @Override
getLayoutDirection(Drawable drawable)205         public int getLayoutDirection(Drawable drawable) {
206             return DrawableCompatApi23.getLayoutDirection(drawable);
207         }
208     }
209 
210     /**
211      * Select the correct implementation to use for the current platform.
212      */
213     static final DrawableImpl IMPL;
214     static {
215         final int version = android.os.Build.VERSION.SDK_INT;
216         if (version >= 23) {
217             IMPL = new MDrawableImpl();
218         } else if (version >= 22) {
219             IMPL = new LollipopMr1DrawableImpl();
220         } else if (version >= 21) {
221             IMPL = new LollipopDrawableImpl();
222         } else if (version >= 19) {
223             IMPL = new KitKatDrawableImpl();
224         } else if (version >= 17) {
225             IMPL = new JellybeanMr1DrawableImpl();
226         } else if (version >= 11) {
227             IMPL = new HoneycombDrawableImpl();
228         } else {
229             IMPL = new BaseDrawableImpl();
230         }
231     }
232 
233     /**
234      * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}.
235      * <p>
236      * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB}
237      * device this method does nothing.
238      *
239      * @param drawable The Drawable against which to invoke the method.
240      */
jumpToCurrentState(Drawable drawable)241     public static void jumpToCurrentState(Drawable drawable) {
242         IMPL.jumpToCurrentState(drawable);
243     }
244 
245     /**
246      * Set whether this Drawable is automatically mirrored when its layout
247      * direction is RTL (right-to left). See
248      * {@link android.util.LayoutDirection}.
249      * <p>
250      * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device
251      * this method does nothing.
252      *
253      * @param drawable The Drawable against which to invoke the method.
254      * @param mirrored Set to true if the Drawable should be mirrored, false if
255      *            not.
256      */
setAutoMirrored(Drawable drawable, boolean mirrored)257     public static void setAutoMirrored(Drawable drawable, boolean mirrored) {
258         IMPL.setAutoMirrored(drawable, mirrored);
259     }
260 
261     /**
262      * Tells if this Drawable will be automatically mirrored when its layout
263      * direction is RTL right-to-left. See {@link android.util.LayoutDirection}.
264      * <p>
265      * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device
266      * this method returns false.
267      *
268      * @param drawable The Drawable against which to invoke the method.
269      * @return boolean Returns true if this Drawable will be automatically
270      *         mirrored.
271      */
isAutoMirrored(Drawable drawable)272     public static boolean isAutoMirrored(Drawable drawable) {
273         return IMPL.isAutoMirrored(drawable);
274     }
275 
276     /**
277      * Specifies the hotspot's location within the drawable.
278      *
279      * @param drawable The Drawable against which to invoke the method.
280      * @param x The X coordinate of the center of the hotspot
281      * @param y The Y coordinate of the center of the hotspot
282      */
setHotspot(Drawable drawable, float x, float y)283     public static void setHotspot(Drawable drawable, float x, float y) {
284         IMPL.setHotspot(drawable, x, y);
285     }
286 
287     /**
288      * Sets the bounds to which the hotspot is constrained, if they should be
289      * different from the drawable bounds.
290      *
291      * @param drawable The Drawable against which to invoke the method.
292      */
setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)293     public static void setHotspotBounds(Drawable drawable, int left, int top,
294             int right, int bottom) {
295         IMPL.setHotspotBounds(drawable, left, top, right, bottom);
296     }
297 
298     /**
299      * Specifies a tint for {@code drawable}.
300      *
301      * @param drawable The Drawable against which to invoke the method.
302      * @param tint     Color to use for tinting this drawable
303      */
setTint(Drawable drawable, int tint)304     public static void setTint(Drawable drawable, int tint) {
305         IMPL.setTint(drawable, tint);
306     }
307 
308     /**
309      * Specifies a tint for {@code drawable} as a color state list.
310      *
311      * @param drawable The Drawable against which to invoke the method.
312      * @param tint     Color state list to use for tinting this drawable, or null to clear the tint
313      */
setTintList(Drawable drawable, ColorStateList tint)314     public static void setTintList(Drawable drawable, ColorStateList tint) {
315         IMPL.setTintList(drawable, tint);
316     }
317 
318     /**
319      * Specifies a tint blending mode for {@code drawable}.
320      *
321      * @param drawable The Drawable against which to invoke the method.
322      * @param tintMode A Porter-Duff blending mode
323      */
setTintMode(Drawable drawable, PorterDuff.Mode tintMode)324     public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
325         IMPL.setTintMode(drawable, tintMode);
326     }
327 
328     /**
329      * Potentially wrap {@code drawable} so that it may be used for tinting across the
330      * different API levels, via the tinting methods in this class.
331      * <p>
332      * If you need to get hold of the original {@link android.graphics.drawable.Drawable} again,
333      * you can use the value returned from {@link #unwrap(Drawable)}.
334      *
335      * @param drawable The Drawable to process
336      * @return A drawable capable of being tinted across all API levels.
337      *
338      * @see #setTint(Drawable, int)
339      * @see #setTintList(Drawable, ColorStateList)
340      * @see #setTintMode(Drawable, PorterDuff.Mode)
341      * @see #unwrap(Drawable)
342      */
wrap(Drawable drawable)343     public static Drawable wrap(Drawable drawable) {
344         return IMPL.wrap(drawable);
345     }
346 
347     /**
348      * Unwrap {@code drawable} if it is the result of a call to {@link #wrap(Drawable)}. If
349      * the {@code drawable} is not the result of a call to {@link #wrap(Drawable)} then
350      * {@code drawable} is returned as-is.
351      *
352      * @param drawable The drawable to unwrap
353      * @return the unwrapped {@link Drawable} or {@code drawable} if it hasn't been wrapped.
354      *
355      * @see #wrap(Drawable)
356      */
unwrap(Drawable drawable)357     public static <T extends Drawable> T unwrap(Drawable drawable) {
358         if (drawable instanceof DrawableWrapper) {
359             return (T) ((DrawableWrapper) drawable).getWrappedDrawable();
360         }
361         return (T) drawable;
362     }
363 
364     /**
365      * Set the layout direction for this drawable. Should be a resolved
366      * layout direction, as the Drawable has no capacity to do the resolution on
367      * its own.
368      *
369      * @param layoutDirection the resolved layout direction for the drawable,
370      *                        either {@link ViewCompat#LAYOUT_DIRECTION_LTR}
371      *                        or {@link ViewCompat#LAYOUT_DIRECTION_RTL}
372      * @see #getLayoutDirection(Drawable)
373      */
setLayoutDirection(Drawable drawable, int layoutDirection)374     public static void setLayoutDirection(Drawable drawable, int layoutDirection) {
375         IMPL.setLayoutDirection(drawable, layoutDirection);
376     }
377 
378     /**
379      * Returns the resolved layout direction for this Drawable.
380      *
381      * @return One of {@link ViewCompat#LAYOUT_DIRECTION_LTR},
382      *         {@link ViewCompat#LAYOUT_DIRECTION_RTL}
383      * @see #setLayoutDirection(Drawable, int)
384      */
getLayoutDirection(Drawable drawable)385     public static int getLayoutDirection(Drawable drawable) {
386         return IMPL.getLayoutDirection(drawable);
387     }
388 }
389