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
17package android.support.v4.graphics.drawable;
18
19import android.content.res.ColorStateList;
20import android.graphics.PorterDuff;
21import android.graphics.drawable.Drawable;
22import 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 */
28public class DrawableCompat {
29    /**
30     * Interface for the full API.
31     */
32    interface DrawableImpl {
33        void jumpToCurrentState(Drawable drawable);
34        void setAutoMirrored(Drawable drawable, boolean mirrored);
35        boolean isAutoMirrored(Drawable drawable);
36        void setHotspot(Drawable drawable, float x, float y);
37        void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom);
38        void setTint(Drawable drawable, int tint);
39        void setTintList(Drawable drawable, ColorStateList tint);
40        void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
41        Drawable wrap(Drawable drawable);
42        void setLayoutDirection(Drawable drawable, int layoutDirection);
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
51        public void jumpToCurrentState(Drawable drawable) {
52        }
53
54        @Override
55        public void setAutoMirrored(Drawable drawable, boolean mirrored) {
56        }
57
58        @Override
59        public boolean isAutoMirrored(Drawable drawable) {
60            return false;
61        }
62
63        @Override
64        public void setHotspot(Drawable drawable, float x, float y) {
65        }
66
67        @Override
68        public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
69        }
70
71        @Override
72        public void setTint(Drawable drawable, int tint) {
73            DrawableCompatBase.setTint(drawable, tint);
74        }
75
76        @Override
77        public void setTintList(Drawable drawable, ColorStateList tint) {
78            DrawableCompatBase.setTintList(drawable, tint);
79        }
80
81        @Override
82        public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
83            DrawableCompatBase.setTintMode(drawable, tintMode);
84        }
85
86        @Override
87        public Drawable wrap(Drawable drawable) {
88            return DrawableCompatBase.wrapForTinting(drawable);
89        }
90
91        @Override
92        public void setLayoutDirection(Drawable drawable, int layoutDirection) {
93            // No op for API < 23
94        }
95
96        @Override
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
107        public void jumpToCurrentState(Drawable drawable) {
108            DrawableCompatHoneycomb.jumpToCurrentState(drawable);
109        }
110
111        @Override
112        public Drawable wrap(Drawable drawable) {
113            return DrawableCompatHoneycomb.wrapForTinting(drawable);
114        }
115    }
116
117    static class JellybeanMr1DrawableImpl extends HoneycombDrawableImpl {
118        @Override
119        public void setLayoutDirection(Drawable drawable, int layoutDirection) {
120            DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection);
121        }
122
123        @Override
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
135        public void setAutoMirrored(Drawable drawable, boolean mirrored) {
136            DrawableCompatKitKat.setAutoMirrored(drawable, mirrored);
137        }
138
139        @Override
140        public boolean isAutoMirrored(Drawable drawable) {
141            return DrawableCompatKitKat.isAutoMirrored(drawable);
142        }
143
144        @Override
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
155        public void setHotspot(Drawable drawable, float x, float y) {
156            DrawableCompatLollipop.setHotspot(drawable, x, y);
157        }
158
159        @Override
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
165        public void setTint(Drawable drawable, int tint) {
166            DrawableCompatLollipop.setTint(drawable, tint);
167        }
168
169        @Override
170        public void setTintList(Drawable drawable, ColorStateList tint) {
171            DrawableCompatLollipop.setTintList(drawable, tint);
172        }
173
174        @Override
175        public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
176            DrawableCompatLollipop.setTintMode(drawable, tintMode);
177        }
178
179        @Override
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
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
200        public void setLayoutDirection(Drawable drawable, int layoutDirection) {
201            DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection);
202        }
203
204        @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
385    public static int getLayoutDirection(Drawable drawable) {
386        return IMPL.getLayoutDirection(drawable);
387    }
388}
389