MediaRouterThemeHelper.java revision 692a547730bbc95ad277d5214ef3d786ce1e499f
1/*
2 * Copyright 2018 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 com.android.support.mediarouter.app;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Color;
22import android.support.annotation.IntDef;
23import android.support.v4.graphics.ColorUtils;
24import android.util.TypedValue;
25import android.view.ContextThemeWrapper;
26import android.view.View;
27
28import com.android.media.update.R;
29
30import java.lang.annotation.Retention;
31import java.lang.annotation.RetentionPolicy;
32
33final class MediaRouterThemeHelper {
34    private static final float MIN_CONTRAST = 3.0f;
35
36    @IntDef({COLOR_DARK_ON_LIGHT_BACKGROUND, COLOR_WHITE_ON_DARK_BACKGROUND})
37    @Retention(RetentionPolicy.SOURCE)
38    private @interface ControllerColorType {}
39
40    static final int COLOR_DARK_ON_LIGHT_BACKGROUND = 0xDE000000; /* Opacity of 87% */
41    static final int COLOR_WHITE_ON_DARK_BACKGROUND = Color.WHITE;
42
43    private MediaRouterThemeHelper() {
44    }
45
46    static Context createThemedButtonContext(Context context) {
47        // Apply base Media Router theme.
48        context = new ContextThemeWrapper(context, getRouterThemeId(context));
49
50        // Apply custom Media Router theme.
51        int style = getThemeResource(context, R.attr.mediaRouteTheme);
52        if (style != 0) {
53            context = new ContextThemeWrapper(context, style);
54        }
55
56        return context;
57    }
58
59    /*
60     * The following two methods are to be used in conjunction. They should be used to prepare
61     * the context and theme for a super class constructor (the latter method relies on the
62     * former method to properly prepare the context):
63     *   super(context = createThemedDialogContext(context, theme),
64     *           createThemedDialogStyle(context));
65     *
66     * It will apply theme in the following order (style lookups will be done in reverse):
67     *   1) Current theme
68     *   2) Supplied theme
69     *   3) Base Media Router theme
70     *   4) Custom Media Router theme, if provided
71     */
72    static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
73        // 1) Current theme is already applied to the context
74
75        // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
76        if (theme == 0) {
77            theme = getThemeResource(context, !alertDialog
78                    ? android.support.v7.appcompat.R.attr.dialogTheme
79                    : android.support.v7.appcompat.R.attr.alertDialogTheme);
80        }
81        //    Apply it
82        context = new ContextThemeWrapper(context, theme);
83
84        // 3) If a custom Media Router theme is provided then apply the base theme
85        if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
86            context = new ContextThemeWrapper(context, getRouterThemeId(context));
87        }
88
89        return context;
90    }
91    // This method should be used in conjunction with the previous method.
92    static int createThemedDialogStyle(Context context) {
93        // 4) Apply the custom Media Router theme
94        int theme = getThemeResource(context, R.attr.mediaRouteTheme);
95        if (theme == 0) {
96            // 3) No custom MediaRouther theme was provided so apply the base theme instead
97            theme = getRouterThemeId(context);
98        }
99
100        return theme;
101    }
102    // END. Previous two methods should be used in conjunction.
103
104    static int getThemeResource(Context context, int attr) {
105        TypedValue value = new TypedValue();
106        return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
107    }
108
109    static float getDisabledAlpha(Context context) {
110        TypedValue value = new TypedValue();
111        return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
112                ? value.getFloat() : 0.5f;
113    }
114
115    static @ControllerColorType int getControllerColor(Context context, int style) {
116        int primaryColor = getThemeColor(context, style,
117                android.support.v7.appcompat.R.attr.colorPrimary);
118        if (primaryColor == 0) {
119            primaryColor = getThemeColor(context, style, android.R.attr.colorPrimary);
120            if (primaryColor == 0) {
121                primaryColor = 0xFF000000;
122            }
123        }
124        if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
125                >= MIN_CONTRAST) {
126            return COLOR_WHITE_ON_DARK_BACKGROUND;
127        }
128        return COLOR_DARK_ON_LIGHT_BACKGROUND;
129    }
130
131    static int getButtonTextColor(Context context) {
132        int primaryColor = getThemeColor(context, 0,
133                android.support.v7.appcompat.R.attr.colorPrimary);
134        int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
135
136        if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
137            // Default to colorAccent if the contrast ratio is low.
138            return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
139        }
140        return primaryColor;
141    }
142
143    static void setMediaControlsBackgroundColor(
144            Context context, View mainControls, View groupControls, boolean hasGroup) {
145        int primaryColor = getThemeColor(context, 0,
146                android.support.v7.appcompat.R.attr.colorPrimary);
147        int primaryDarkColor = getThemeColor(context, 0,
148                android.support.v7.appcompat.R.attr.colorPrimaryDark);
149        if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
150            // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
151            // the white dialog and use the primary color for the group controls.
152            primaryDarkColor = primaryColor;
153            primaryColor = Color.WHITE;
154        }
155        mainControls.setBackgroundColor(primaryColor);
156        groupControls.setBackgroundColor(primaryDarkColor);
157        // Also store the background colors to the view tags. They are used in
158        // setVolumeSliderColor() below.
159        mainControls.setTag(primaryColor);
160        groupControls.setTag(primaryDarkColor);
161    }
162
163    static void setVolumeSliderColor(
164            Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
165        int controllerColor = getControllerColor(context, 0);
166        if (Color.alpha(controllerColor) != 0xFF) {
167            // Composite with the background in order not to show the underlying progress bar
168            // through the thumb.
169            int backgroundColor = (int) backgroundView.getTag();
170            controllerColor = ColorUtils.compositeColors(controllerColor, backgroundColor);
171        }
172        volumeSlider.setColor(controllerColor);
173    }
174
175    private static boolean isLightTheme(Context context) {
176        TypedValue value = new TypedValue();
177        return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
178                value, true) && value.data != 0;
179    }
180
181    private static int getThemeColor(Context context, int style, int attr) {
182        if (style != 0) {
183            int[] attrs = { attr };
184            TypedArray ta = context.obtainStyledAttributes(style, attrs);
185            int color = ta.getColor(0, 0);
186            ta.recycle();
187            if (color != 0) {
188                return color;
189            }
190        }
191        TypedValue value = new TypedValue();
192        context.getTheme().resolveAttribute(attr, value, true);
193        if (value.resourceId != 0) {
194            return context.getResources().getColor(value.resourceId);
195        }
196        return value.data;
197    }
198
199    private static int getRouterThemeId(Context context) {
200        int themeId;
201        if (isLightTheme(context)) {
202            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
203                themeId = R.style.Theme_MediaRouter_Light;
204            } else {
205                themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
206            }
207        } else {
208            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
209                themeId = R.style.Theme_MediaRouter_LightControlPanel;
210            } else {
211                themeId = R.style.Theme_MediaRouter;
212            }
213        }
214        return themeId;
215    }
216}
217