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