1 /* 2 * Copyright (C) 2019 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 17 package com.google.android.setupdesign.util; 18 19 import static com.google.android.setupcompat.util.BuildCompatUtils.isAtLeastS; 20 21 import android.content.Context; 22 import android.graphics.Typeface; 23 import android.graphics.drawable.Drawable; 24 import android.graphics.drawable.VectorDrawable; 25 import android.os.Build; 26 import android.os.Build.VERSION; 27 import android.os.Build.VERSION_CODES; 28 import android.util.Log; 29 import android.util.TypedValue; 30 import android.view.ViewGroup; 31 import android.view.ViewGroup.LayoutParams; 32 import android.view.ViewTreeObserver; 33 import android.widget.FrameLayout; 34 import android.widget.ImageView; 35 import android.widget.ImageView.ScaleType; 36 import android.widget.LinearLayout; 37 import android.widget.ProgressBar; 38 import android.widget.TextView; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.VisibleForTesting; 41 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; 42 import com.google.android.setupcompat.partnerconfig.PartnerConfig; 43 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; 44 import com.google.android.setupdesign.R; 45 import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs; 46 47 /** 48 * Applies the partner customization for the header area widgets. The user needs to check if the 49 * header area widgets should apply partner heavy theme or light theme before calling these methods. 50 */ 51 public final class HeaderAreaStyler { 52 53 private static final String TAG = "HeaderAreaStyler"; 54 55 @VisibleForTesting 56 static final String WARN_TO_USE_DRAWABLE = 57 "To achieve scaling icon in SetupDesign lib, should use vector drawable icon from "; 58 59 /** 60 * Applies the partner style of header text to the given textView {@code header}. 61 * 62 * @param header A header text would apply partner style 63 */ applyPartnerCustomizationHeaderStyle(@ullable TextView header)64 public static void applyPartnerCustomizationHeaderStyle(@Nullable TextView header) { 65 66 if (header == null) { 67 return; 68 } 69 TextViewPartnerStyler.applyPartnerCustomizationStyle( 70 header, 71 new TextPartnerConfigs( 72 PartnerConfig.CONFIG_HEADER_TEXT_COLOR, 73 /* textLinkedColorConfig= */ null, 74 PartnerConfig.CONFIG_HEADER_TEXT_SIZE, 75 PartnerConfig.CONFIG_HEADER_FONT_FAMILY, 76 PartnerConfig.CONFIG_HEADER_FONT_WEIGHT, 77 /* textLinkFontFamilyConfig= */ null, 78 PartnerConfig.CONFIG_HEADER_TEXT_MARGIN_TOP, 79 PartnerConfig.CONFIG_HEADER_TEXT_MARGIN_BOTTOM, 80 PartnerConfig.CONFIG_HEADER_FONT_VARIATION_SETTINGS, 81 PartnerStyleHelper.getLayoutGravity(header.getContext()))); 82 } 83 84 /** 85 * Applies the partner heavy style of description text to the given textView {@code description}. 86 * 87 * @param description A description text would apply partner heavy style 88 */ applyPartnerCustomizationDescriptionHeavyStyle( @ullable TextView description)89 public static void applyPartnerCustomizationDescriptionHeavyStyle( 90 @Nullable TextView description) { 91 92 if (description == null) { 93 return; 94 } 95 TextViewPartnerStyler.applyPartnerCustomizationStyle( 96 description, 97 new TextPartnerConfigs( 98 PartnerConfig.CONFIG_DESCRIPTION_TEXT_COLOR, 99 PartnerConfig.CONFIG_DESCRIPTION_LINK_TEXT_COLOR, 100 PartnerConfig.CONFIG_DESCRIPTION_TEXT_SIZE, 101 PartnerConfig.CONFIG_DESCRIPTION_FONT_FAMILY, 102 PartnerConfig.CONFIG_DESCRIPTION_FONT_WEIGHT, 103 PartnerConfig.CONFIG_DESCRIPTION_LINK_FONT_FAMILY, 104 PartnerConfig.CONFIG_DESCRIPTION_TEXT_MARGIN_TOP, 105 PartnerConfig.CONFIG_DESCRIPTION_TEXT_MARGIN_BOTTOM, 106 PartnerStyleHelper.getLayoutGravity(description.getContext()))); 107 } 108 applyPartnerCustomizationAccountStyle( ImageView avatar, TextView name, LinearLayout container)109 public static void applyPartnerCustomizationAccountStyle( 110 ImageView avatar, TextView name, LinearLayout container) { 111 if (avatar == null || name == null) { 112 return; 113 } 114 115 Context context = avatar.getContext(); 116 117 ViewGroup.LayoutParams lpIcon = avatar.getLayoutParams(); 118 if (lpIcon instanceof ViewGroup.MarginLayoutParams) { 119 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lpIcon; 120 121 int rightMargin = 122 (int) 123 PartnerConfigHelper.get(context) 124 .getDimension(context, PartnerConfig.CONFIG_ACCOUNT_AVATAR_MARGIN_END); 125 mlp.setMargins(mlp.leftMargin, mlp.topMargin, rightMargin, mlp.bottomMargin); 126 } 127 128 int maxHeight = 129 (int) 130 PartnerConfigHelper.get(context) 131 .getDimension(context, PartnerConfig.CONFIG_ACCOUNT_AVATAR_SIZE, 132 context.getResources().getDimension(R.dimen.sud_account_avatar_max_height)); 133 avatar.setMaxHeight(maxHeight); 134 135 int textSize = 136 (int) 137 PartnerConfigHelper.get(context) 138 .getDimension( 139 context, 140 PartnerConfig.CONFIG_ACCOUNT_NAME_TEXT_SIZE, 141 context.getResources().getDimension(R.dimen.sud_account_name_text_size)); 142 name.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 143 144 String textFamily = 145 PartnerConfigHelper.get(context) 146 .getString(context, PartnerConfig.CONFIG_ACCOUNT_NAME_FONT_FAMILY); 147 Typeface font = Typeface.create(textFamily, Typeface.NORMAL); 148 if (font != null) { 149 name.setTypeface(font); 150 } 151 152 int gravity = PartnerStyleHelper.getLayoutGravity(container.getContext()); 153 container.setGravity(gravity); 154 } 155 156 /** 157 * Applies the partner style of header area to the given layout {@code headerArea}. The theme 158 * should set partner heavy theme first, and then the partner style of header would be applied. As 159 * for the margin bottom of header, it would also be appied when heavy theme parter config is 160 * enabled. 161 * 162 * @param headerArea A ViewGroup would apply the partner style of header area 163 */ applyPartnerCustomizationHeaderAreaStyle(ViewGroup headerArea)164 public static void applyPartnerCustomizationHeaderAreaStyle(ViewGroup headerArea) { 165 if (headerArea == null) { 166 return; 167 } 168 169 Context context = headerArea.getContext(); 170 int color = 171 PartnerConfigHelper.get(context) 172 .getColor(context, PartnerConfig.CONFIG_HEADER_AREA_BACKGROUND_COLOR); 173 headerArea.setBackgroundColor(color); 174 175 if (PartnerConfigHelper.get(context) 176 .isPartnerConfigAvailable(PartnerConfig.CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM)) { 177 final ViewGroup.LayoutParams lp = headerArea.getLayoutParams(); 178 if (lp instanceof ViewGroup.MarginLayoutParams) { 179 final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; 180 181 int bottomMargin = 182 (int) 183 PartnerConfigHelper.get(context) 184 .getDimension(context, PartnerConfig.CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM); 185 mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, bottomMargin); 186 headerArea.setLayoutParams(lp); 187 } 188 } 189 } 190 applyPartnerCustomizationProgressBarStyle(@ullable ProgressBar progressBar)191 public static void applyPartnerCustomizationProgressBarStyle(@Nullable ProgressBar progressBar) { 192 if (progressBar == null) { 193 return; 194 } 195 Context context = progressBar.getContext(); 196 final ViewGroup.LayoutParams lp = progressBar.getLayoutParams(); 197 198 if (lp instanceof ViewGroup.MarginLayoutParams) { 199 final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; 200 int marginTop = mlp.topMargin; 201 if (PartnerConfigHelper.get(context) 202 .isPartnerConfigAvailable(PartnerConfig.CONFIG_PROGRESS_BAR_MARGIN_TOP)) { 203 marginTop = 204 (int) 205 PartnerConfigHelper.get(context) 206 .getDimension( 207 context, 208 PartnerConfig.CONFIG_PROGRESS_BAR_MARGIN_TOP, 209 context.getResources().getDimension(R.dimen.sud_progress_bar_margin_top)); 210 } 211 int marginBottom = mlp.bottomMargin; 212 if (PartnerConfigHelper.get(context) 213 .isPartnerConfigAvailable(PartnerConfig.CONFIG_PROGRESS_BAR_MARGIN_BOTTOM)) { 214 marginBottom = 215 (int) 216 PartnerConfigHelper.get(context) 217 .getDimension( 218 context, 219 PartnerConfig.CONFIG_PROGRESS_BAR_MARGIN_BOTTOM, 220 context 221 .getResources() 222 .getDimension(R.dimen.sud_progress_bar_margin_bottom)); 223 } 224 225 if (marginTop != mlp.topMargin || marginBottom != mlp.bottomMargin) { 226 mlp.setMargins(mlp.leftMargin, marginTop, mlp.rightMargin, marginBottom); 227 } 228 } 229 } 230 231 /** 232 * Applies the partner style of header icon to the given {@code iconImage}. It needs to check if 233 * it should apply partner resource first, and then the partner icon size would be applied. 234 * 235 * @param iconImage A ImageView would apply the partner style of header icon 236 * @param iconContainer The container of the header icon 237 */ applyPartnerCustomizationIconStyle( @ullable ImageView iconImage, FrameLayout iconContainer)238 public static void applyPartnerCustomizationIconStyle( 239 @Nullable ImageView iconImage, FrameLayout iconContainer) { 240 if (iconImage == null || iconContainer == null) { 241 return; 242 } 243 244 Context context = iconImage.getContext(); 245 int reducedIconHeight = 0; 246 int gravity = PartnerStyleHelper.getLayoutGravity(context); 247 // Skip the partner customization when isGlifExpressiveEnabled is true 248 if (gravity != 0 && !PartnerConfigHelper.isGlifExpressiveEnabled(context)) { 249 setGravity(iconImage, gravity); 250 } 251 252 if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(PartnerConfig.CONFIG_ICON_SIZE)) { 253 checkImageType(iconImage); 254 255 final ViewGroup.LayoutParams lpIcon = iconImage.getLayoutParams(); 256 257 lpIcon.height = 258 (int) 259 PartnerConfigHelper.get(context) 260 .getDimension(context, PartnerConfig.CONFIG_ICON_SIZE); 261 262 lpIcon.width = LayoutParams.WRAP_CONTENT; 263 iconImage.setScaleType(ScaleType.FIT_CENTER); 264 265 Drawable drawable = iconImage.getDrawable(); 266 if (drawable != null && drawable.getIntrinsicWidth() > (2 * drawable.getIntrinsicHeight())) { 267 int fixedIconHeight = 268 (int) context.getResources().getDimension(R.dimen.sud_horizontal_icon_height); 269 if (lpIcon.height > fixedIconHeight) { 270 reducedIconHeight = lpIcon.height - fixedIconHeight; 271 lpIcon.height = fixedIconHeight; 272 } 273 } 274 } 275 276 final ViewGroup.LayoutParams lp = iconContainer.getLayoutParams(); 277 boolean partnerConfigAvailable = 278 PartnerConfigHelper.get(context) 279 .isPartnerConfigAvailable(PartnerConfig.CONFIG_ICON_MARGIN_TOP); 280 if (partnerConfigAvailable && lp instanceof ViewGroup.MarginLayoutParams) { 281 final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; 282 int topMargin = 283 (int) 284 PartnerConfigHelper.get(context) 285 .getDimension(context, PartnerConfig.CONFIG_ICON_MARGIN_TOP); 286 topMargin += reducedIconHeight; 287 mlp.setMargins(mlp.leftMargin, topMargin, mlp.rightMargin, mlp.bottomMargin); 288 } 289 } 290 291 /** 292 * Applies the partner style of back button to center align the icon. It needs to check if it 293 * should apply partner resource first, and then adjust the margin top according to the partner 294 * icon size. 295 * 296 * @param buttonContainer The container of the back button 297 */ applyPartnerCustomizationBackButtonStyle(FrameLayout buttonContainer)298 public static void applyPartnerCustomizationBackButtonStyle(FrameLayout buttonContainer) { 299 if (buttonContainer == null) { 300 return; 301 } 302 303 Context context = buttonContainer.getContext(); 304 int heightDifference = 0; 305 306 // Calculate the difference of icon height & button height 307 ViewGroup.LayoutParams lpButtonContainer = buttonContainer.getLayoutParams(); 308 int backButtonHeight = 309 (int) context.getResources().getDimension(R.dimen.sud_glif_expressive_back_button_height); 310 int iconHeight = getPartnerConfigDimension(context, PartnerConfig.CONFIG_ICON_SIZE, 0); 311 if (iconHeight > backButtonHeight) { 312 heightDifference = iconHeight - backButtonHeight; 313 } 314 315 // Adjust margin top of button container to vertically center align the icon 316 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lpButtonContainer; 317 // The default topMargin should align with the icon margin top 318 int topMargin = 319 getPartnerConfigDimension(context, PartnerConfig.CONFIG_ICON_MARGIN_TOP, mlp.topMargin); 320 int adjustedTopMargin = topMargin; 321 if (heightDifference != 0) { 322 adjustedTopMargin = topMargin + heightDifference / 2; 323 } 324 325 if (adjustedTopMargin != mlp.topMargin) { 326 FrameLayout.LayoutParams params = 327 new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 328 params.setMargins(mlp.leftMargin, adjustedTopMargin, mlp.rightMargin, mlp.bottomMargin); 329 buttonContainer.setLayoutParams(params); 330 } 331 } 332 getPartnerConfigDimension( Context context, PartnerConfig config, int defaultValue)333 private static int getPartnerConfigDimension( 334 Context context, PartnerConfig config, int defaultValue) { 335 if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(config)) { 336 return (int) PartnerConfigHelper.get(context).getDimension(context, config); 337 } 338 339 return defaultValue; 340 } 341 checkImageType(ImageView imageView)342 private static void checkImageType(ImageView imageView) { 343 ViewTreeObserver vto = imageView.getViewTreeObserver(); 344 vto.addOnPreDrawListener( 345 new ViewTreeObserver.OnPreDrawListener() { 346 @Override 347 public boolean onPreDraw() { 348 imageView.getViewTreeObserver().removeOnPreDrawListener(this); 349 // TODO: Remove when Partners all used Drawable icon image and never use 350 if (isAtLeastS() 351 && !(imageView.getDrawable() == null 352 || (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP 353 && imageView.getDrawable() instanceof VectorDrawable) 354 || imageView.getDrawable() instanceof VectorDrawableCompat) 355 && (Build.TYPE.equals("userdebug") || Build.TYPE.equals("eng"))) { 356 Log.w(TAG, WARN_TO_USE_DRAWABLE + imageView.getContext().getPackageName()); 357 } 358 return true; 359 } 360 }); 361 } 362 setGravity(ImageView icon, int gravity)363 private static void setGravity(ImageView icon, int gravity) { 364 if (icon.getLayoutParams() instanceof FrameLayout.LayoutParams) { 365 FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) icon.getLayoutParams(); 366 layoutParams.gravity = gravity; 367 icon.setLayoutParams(layoutParams); 368 } 369 } 370 HeaderAreaStyler()371 private HeaderAreaStyler() {} 372 } 373