MediaRouterThemeHelper.java revision d5b1f00dbd91f5bc64f30891ec7a35f28274dbb0
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, 78 !alertDialog ? android.R.attr.dialogTheme : android.R.attr.alertDialogTheme); 79 } 80 // Apply it 81 context = new ContextThemeWrapper(context, theme); 82 83 // 3) If a custom Media Router theme is provided then apply the base theme 84 if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) { 85 context = new ContextThemeWrapper(context, getRouterThemeId(context)); 86 } 87 88 return context; 89 } 90 // This method should be used in conjunction with the previous method. 91 static int createThemedDialogStyle(Context context) { 92 // 4) Apply the custom Media Router theme 93 int theme = getThemeResource(context, R.attr.mediaRouteTheme); 94 if (theme == 0) { 95 // 3) No custom MediaRouter theme was provided so apply the base theme instead 96 theme = getRouterThemeId(context); 97 } 98 99 return theme; 100 } 101 // END. Previous two methods should be used in conjunction. 102 103 static int getThemeResource(Context context, int attr) { 104 TypedValue value = new TypedValue(); 105 return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0; 106 } 107 108 static float getDisabledAlpha(Context context) { 109 TypedValue value = new TypedValue(); 110 return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true) 111 ? value.getFloat() : 0.5f; 112 } 113 114 static @ControllerColorType int getControllerColor(Context context, int style) { 115 int primaryColor = getThemeColor(context, style, android.R.attr.colorPrimary); 116 if (primaryColor == 0) { 117 primaryColor = getThemeColor(context, style, android.R.attr.colorPrimary); 118 if (primaryColor == 0) { 119 primaryColor = 0xFF000000; 120 } 121 } 122 if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor) 123 >= MIN_CONTRAST) { 124 return COLOR_WHITE_ON_DARK_BACKGROUND; 125 } 126 return COLOR_DARK_ON_LIGHT_BACKGROUND; 127 } 128 129 static int getButtonTextColor(Context context) { 130 int primaryColor = getThemeColor(context, 0, android.R.attr.colorPrimary); 131 int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground); 132 133 if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) { 134 // Default to colorAccent if the contrast ratio is low. 135 return getThemeColor(context, 0, android.R.attr.colorAccent); 136 } 137 return primaryColor; 138 } 139 140 static void setMediaControlsBackgroundColor( 141 Context context, View mainControls, View groupControls, boolean hasGroup) { 142 int primaryColor = getThemeColor(context, 0, android.R.attr.colorPrimary); 143 int primaryDarkColor = getThemeColor(context, 0, android.R.attr.colorPrimaryDark); 144 if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) { 145 // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model 146 // the white dialog and use the primary color for the group controls. 147 primaryDarkColor = primaryColor; 148 primaryColor = Color.WHITE; 149 } 150 mainControls.setBackgroundColor(primaryColor); 151 groupControls.setBackgroundColor(primaryDarkColor); 152 // Also store the background colors to the view tags. They are used in 153 // setVolumeSliderColor() below. 154 mainControls.setTag(primaryColor); 155 groupControls.setTag(primaryDarkColor); 156 } 157 158 static void setVolumeSliderColor( 159 Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) { 160 int controllerColor = getControllerColor(context, 0); 161 if (Color.alpha(controllerColor) != 0xFF) { 162 // Composite with the background in order not to show the underlying progress bar 163 // through the thumb. 164 int backgroundColor = (int) backgroundView.getTag(); 165 controllerColor = ColorUtils.compositeColors(controllerColor, backgroundColor); 166 } 167 volumeSlider.setColor(controllerColor); 168 } 169 170 private static boolean isLightTheme(Context context) { 171 TypedValue value = new TypedValue(); 172 // TODO(sungsoo): Switch to com.android.internal.R.attr.isLightTheme 173 return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme, 174 value, true) && value.data != 0; 175 } 176 177 private static int getThemeColor(Context context, int style, int attr) { 178 if (style != 0) { 179 int[] attrs = { attr }; 180 TypedArray ta = context.obtainStyledAttributes(style, attrs); 181 int color = ta.getColor(0, 0); 182 ta.recycle(); 183 if (color != 0) { 184 return color; 185 } 186 } 187 TypedValue value = new TypedValue(); 188 context.getTheme().resolveAttribute(attr, value, true); 189 if (value.resourceId != 0) { 190 return context.getResources().getColor(value.resourceId); 191 } 192 return value.data; 193 } 194 195 static int getRouterThemeId(Context context) { 196 int themeId; 197 if (isLightTheme(context)) { 198 if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) { 199 themeId = R.style.Theme_MediaRouter_Light; 200 } else { 201 themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel; 202 } 203 } else { 204 if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) { 205 themeId = R.style.Theme_MediaRouter_LightControlPanel; 206 } else { 207 themeId = R.style.Theme_MediaRouter; 208 } 209 } 210 return themeId; 211 } 212} 213