• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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