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.v7.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.support.v7.mediarouter.R;
25import android.util.TypedValue;
26import android.view.ContextThemeWrapper;
27import android.view.View;
28
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31
32final class MediaRouterThemeHelper {
33    private static final float MIN_CONTRAST = 3.0f;
34
35    @IntDef({COLOR_DARK_ON_LIGHT_BACKGROUND, COLOR_WHITE_ON_DARK_BACKGROUND})
36    @Retention(RetentionPolicy.SOURCE)
37    private @interface ControllerColorType {}
38
39    static final int COLOR_DARK_ON_LIGHT_BACKGROUND = 0xDE000000; /* Opacity of 87% */
40    static final int COLOR_WHITE_ON_DARK_BACKGROUND = Color.WHITE;
41
42    private MediaRouterThemeHelper() {
43    }
44
45    /**
46     * Creates a themed context based on the explicit style resource or the parent context's default
47     * theme.
48     * <p>
49     * The theme which will be applied on top of the parent {@code context}'s theme is determined
50     * by the primary color defined in the given {@code style}, or in the parent {@code context}.
51     *
52     * @param context the parent context
53     * @param style the resource ID of the style against which to inflate this context, or
54     *              {@code 0} to use the parent {@code context}'s default theme.
55     * @return The themed context.
56     */
57    public static Context createThemedContext(Context context, int style) {
58        // First, apply dialog property overlay.
59
60        int theme;
61        if (isLightTheme(context)) {
62            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
63                theme = R.style.Theme_MediaRouter_Light;
64            } else {
65                theme = R.style.Theme_MediaRouter_Light_DarkControlPanel;
66            }
67        } else {
68            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
69                theme = R.style.Theme_MediaRouter_LightControlPanel;
70            } else {
71                theme = R.style.Theme_MediaRouter;
72            }
73        }
74        int mediaRouteThemeResId = getThemeResource(context, R.attr.mediaRouteTheme);
75        Context themedContext = new ContextThemeWrapper(context, theme);
76        if (mediaRouteThemeResId != 0) {
77            themedContext = new ContextThemeWrapper(themedContext, mediaRouteThemeResId);
78        }
79        return themedContext;
80    }
81
82    public static int getThemeResource(Context context, int attr) {
83        TypedValue value = new TypedValue();
84        return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
85    }
86
87    public static float getDisabledAlpha(Context context) {
88        TypedValue value = new TypedValue();
89        return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
90                ? value.getFloat() : 0.5f;
91    }
92
93    public static @ControllerColorType int getControllerColor(Context context, int style) {
94        int primaryColor = getThemeColor(context, style,
95                android.support.v7.appcompat.R.attr.colorPrimary);
96        if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
97                >= MIN_CONTRAST) {
98            return COLOR_WHITE_ON_DARK_BACKGROUND;
99        }
100        return COLOR_DARK_ON_LIGHT_BACKGROUND;
101    }
102
103    public static int getButtonTextColor(Context context) {
104        int primaryColor = getThemeColor(context, 0,
105                android.support.v7.appcompat.R.attr.colorPrimary);
106        int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
107
108        if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
109            // Default to colorAccent if the contrast ratio is low.
110            return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
111        }
112        return primaryColor;
113    }
114
115    public static void setMediaControlsBackgroundColor(
116            Context context, View mainControls, View groupControls, boolean hasGroup) {
117        int primaryColor = getThemeColor(context, 0,
118                android.support.v7.appcompat.R.attr.colorPrimary);
119        int primaryDarkColor = getThemeColor(context, 0,
120                android.support.v7.appcompat.R.attr.colorPrimaryDark);
121        if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
122            // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
123            // the white dialog and use the primary color for the group controls.
124            primaryDarkColor = primaryColor;
125            primaryColor = Color.WHITE;
126        }
127        mainControls.setBackgroundColor(primaryColor);
128        groupControls.setBackgroundColor(primaryDarkColor);
129        // Also store the background colors to the view tags. They are used in
130        // setVolumeSliderColor() below.
131        mainControls.setTag(primaryColor);
132        groupControls.setTag(primaryDarkColor);
133    }
134
135    public static void setVolumeSliderColor(
136            Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
137        int controllerColor = getControllerColor(context, 0);
138        if (Color.alpha(controllerColor) != 0xFF) {
139            // Composite with the background in order not to show the underlying progress bar
140            // through the thumb.
141            int backgroundColor = (int) backgroundView.getTag();
142            controllerColor = ColorUtils.compositeColors(controllerColor, backgroundColor);
143        }
144        volumeSlider.setColor(controllerColor);
145    }
146
147    // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance.
148    public static int getAlertDialogResolvedTheme(Context context, int themeResId) {
149        if (themeResId >= 0x01000000) {   // start of real resource IDs.
150            return themeResId;
151        } else {
152            TypedValue outValue = new TypedValue();
153            context.getTheme().resolveAttribute(
154                    android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true);
155            return outValue.resourceId;
156        }
157    }
158
159    private static boolean isLightTheme(Context context) {
160        TypedValue value = new TypedValue();
161        return context.getTheme().resolveAttribute(
162                android.support.v7.appcompat.R.attr.isLightTheme, value, true)
163                && value.data != 0;
164    }
165
166    private static int getThemeColor(Context context, int style, int attr) {
167        if (style != 0) {
168            int[] attrs = { attr };
169            TypedArray ta = context.obtainStyledAttributes(style, attrs);
170            int color = ta.getColor(0, 0);
171            ta.recycle();
172            if (color != 0) {
173                return color;
174            }
175        }
176        TypedValue value = new TypedValue();
177        context.getTheme().resolveAttribute(attr, value, true);
178        if (value.resourceId != 0) {
179            return context.getResources().getColor(value.resourceId);
180        }
181        return value.data;
182    }
183}
184