1/*
2 * Copyright (C) 2015 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.widget;
18
19import android.content.res.ColorStateList;
20import android.graphics.PorterDuff;
21import android.graphics.drawable.Drawable;
22import android.os.Build;
23import android.support.annotation.NonNull;
24import android.support.annotation.Nullable;
25import android.support.annotation.RequiresApi;
26import android.support.v4.graphics.drawable.DrawableCompat;
27import android.util.Log;
28import android.widget.CompoundButton;
29
30import java.lang.reflect.Field;
31
32/**
33 * Helper for accessing {@link android.widget.CompoundButton}.
34 */
35public final class CompoundButtonCompat {
36
37    private static final CompoundButtonCompatBaseImpl IMPL;
38
39    static {
40        if (Build.VERSION.SDK_INT >= 23) {
41            IMPL = new CompoundButtonCompatApi23Impl();
42        } else if (Build.VERSION.SDK_INT >= 21) {
43            IMPL = new CompoundButtonCompatApi21Impl();
44        } else {
45            IMPL = new CompoundButtonCompatBaseImpl();
46        }
47    }
48
49    static class CompoundButtonCompatBaseImpl {
50        private static final String TAG = "CompoundButtonCompat";
51
52        private static Field sButtonDrawableField;
53        private static boolean sButtonDrawableFieldFetched;
54
55        public void setButtonTintList(CompoundButton button, ColorStateList tint) {
56            if (button instanceof TintableCompoundButton) {
57                ((TintableCompoundButton) button).setSupportButtonTintList(tint);
58            }
59        }
60
61        public ColorStateList getButtonTintList(CompoundButton button) {
62            if (button instanceof TintableCompoundButton) {
63                return ((TintableCompoundButton) button).getSupportButtonTintList();
64            }
65            return null;
66        }
67
68        public void setButtonTintMode(CompoundButton button, PorterDuff.Mode tintMode) {
69            if (button instanceof TintableCompoundButton) {
70                ((TintableCompoundButton) button).setSupportButtonTintMode(tintMode);
71            }
72        }
73
74        public PorterDuff.Mode getButtonTintMode(CompoundButton button) {
75            if (button instanceof TintableCompoundButton) {
76                return ((TintableCompoundButton) button).getSupportButtonTintMode();
77            }
78            return null;
79        }
80
81        public Drawable getButtonDrawable(CompoundButton button) {
82            if (!sButtonDrawableFieldFetched) {
83                try {
84                    sButtonDrawableField = CompoundButton.class.getDeclaredField("mButtonDrawable");
85                    sButtonDrawableField.setAccessible(true);
86                } catch (NoSuchFieldException e) {
87                    Log.i(TAG, "Failed to retrieve mButtonDrawable field", e);
88                }
89                sButtonDrawableFieldFetched = true;
90            }
91
92            if (sButtonDrawableField != null) {
93                try {
94                    return (Drawable) sButtonDrawableField.get(button);
95                } catch (IllegalAccessException e) {
96                    Log.i(TAG, "Failed to get button drawable via reflection", e);
97                    sButtonDrawableField = null;
98                }
99            }
100            return null;
101        }
102    }
103
104    @RequiresApi(21)
105    static class CompoundButtonCompatApi21Impl extends CompoundButtonCompatBaseImpl {
106        @Override
107        public void setButtonTintList(CompoundButton button, ColorStateList tint) {
108            button.setButtonTintList(tint);
109        }
110
111        @Override
112        public ColorStateList getButtonTintList(CompoundButton button) {
113            return button.getButtonTintList();
114        }
115
116        @Override
117        public void setButtonTintMode(CompoundButton button, PorterDuff.Mode tintMode) {
118            button.setButtonTintMode(tintMode);
119        }
120
121        @Override
122        public PorterDuff.Mode getButtonTintMode(CompoundButton button) {
123            return button.getButtonTintMode();
124        }
125    }
126
127    @RequiresApi(23)
128    static class CompoundButtonCompatApi23Impl extends CompoundButtonCompatApi21Impl {
129        @Override
130        public Drawable getButtonDrawable(CompoundButton button) {
131            return button.getButtonDrawable();
132        }
133    }
134
135    private CompoundButtonCompat() {}
136
137    /**
138     * Applies a tint to the button drawable. Does not modify the current tint
139     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
140     * <p>
141     * Subsequent calls to {@link CompoundButton#setButtonDrawable(Drawable)} should
142     * automatically mutate the drawable and apply the specified tint and tint
143     * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
144     *
145     * @param tint the tint to apply, may be {@code null} to clear tint
146     *
147     * @see #setButtonTintList(CompoundButton, ColorStateList)
148     */
149    public static void setButtonTintList(@NonNull CompoundButton button, @Nullable ColorStateList tint) {
150        IMPL.setButtonTintList(button, tint);
151    }
152
153    /**
154     * Returns the tint applied to the button drawable
155     *
156     * @see #setButtonTintList(CompoundButton, ColorStateList)
157     */
158    @Nullable
159    public static ColorStateList getButtonTintList(@NonNull CompoundButton button) {
160        return IMPL.getButtonTintList(button);
161    }
162
163    /**
164     * Specifies the blending mode used to apply the tint specified by
165     * {@link #setButtonTintList(CompoundButton, ColorStateList)}} to the button drawable. The
166     * default mode is {@link PorterDuff.Mode#SRC_IN}.
167     *
168     * @param tintMode the blending mode used to apply the tint, may be
169     *                 {@code null} to clear tint
170     *
171     * @see #getButtonTintMode(CompoundButton)
172     * @see DrawableCompat#setTintMode(Drawable, PorterDuff.Mode)
173     */
174    public static void setButtonTintMode(@NonNull CompoundButton button,
175            @Nullable PorterDuff.Mode tintMode) {
176        IMPL.setButtonTintMode(button, tintMode);
177    }
178
179    /**
180     * @return the blending mode used to apply the tint to the button drawable
181     * @attr name android:buttonTintMode
182     * @see #setButtonTintMode(CompoundButton, PorterDuff.Mode)
183     */
184    @Nullable
185    public static PorterDuff.Mode getButtonTintMode(@NonNull CompoundButton button) {
186        return IMPL.getButtonTintMode(button);
187    }
188
189    /**
190     * Returns the drawable used as the compound button image
191     *
192     * @see CompoundButton#setButtonDrawable(Drawable)
193     */
194    @Nullable
195    public static Drawable getButtonDrawable(@NonNull CompoundButton button) {
196        return IMPL.getButtonDrawable(button);
197    }
198}
199