• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.content.res.TypedArray;
23 import android.graphics.drawable.ColorDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 import android.os.Build.VERSION_CODES;
27 import android.util.AttributeSet;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewStub;
32 import android.widget.ProgressBar;
33 import android.widget.ScrollView;
34 import android.widget.TextView;
35 import androidx.annotation.ColorInt;
36 import androidx.annotation.LayoutRes;
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.StringRes;
40 import com.google.android.setupcompat.PartnerCustomizationLayout;
41 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
42 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
43 import com.google.android.setupcompat.template.StatusBarMixin;
44 import com.google.android.setupdesign.template.DescriptionMixin;
45 import com.google.android.setupdesign.template.HeaderMixin;
46 import com.google.android.setupdesign.template.IconMixin;
47 import com.google.android.setupdesign.template.IllustrationProgressMixin;
48 import com.google.android.setupdesign.template.ProfileMixin;
49 import com.google.android.setupdesign.template.ProgressBarMixin;
50 import com.google.android.setupdesign.template.RequireScrollMixin;
51 import com.google.android.setupdesign.template.ScrollViewScrollHandlingDelegate;
52 import com.google.android.setupdesign.util.DescriptionStyler;
53 import com.google.android.setupdesign.util.LayoutStyler;
54 
55 /**
56  * Layout for the GLIF theme used in Setup Wizard for N.
57  *
58  * <p>Example usage:
59  *
60  * <pre>{@code
61  * &lt;com.google.android.setupdesign.GlifLayout
62  *     xmlns:android="http://schemas.android.com/apk/res/android"
63  *     xmlns:app="http://schemas.android.com/apk/res-auto"
64  *     android:layout_width="match_parent"
65  *     android:layout_height="match_parent"
66  *     android:icon="@drawable/my_icon"
67  *     app:sucHeaderText="@string/my_title">
68  *
69  *     &lt;!-- Content here -->
70  *
71  * &lt;/com.google.android.setupdesign.GlifLayout>
72  * }</pre>
73  */
74 public class GlifLayout extends PartnerCustomizationLayout {
75 
76   private ColorStateList primaryColor;
77 
78   private boolean backgroundPatterned = true;
79 
80   private boolean applyPartnerHeavyThemeResource = false;
81 
82   /** The color of the background. If null, the color will inherit from primaryColor. */
83   @Nullable private ColorStateList backgroundBaseColor;
84 
GlifLayout(Context context)85   public GlifLayout(Context context) {
86     this(context, 0, 0);
87   }
88 
GlifLayout(Context context, int template)89   public GlifLayout(Context context, int template) {
90     this(context, template, 0);
91   }
92 
GlifLayout(Context context, int template, int containerId)93   public GlifLayout(Context context, int template, int containerId) {
94     super(context, template, containerId);
95     init(null, R.attr.sudLayoutTheme);
96   }
97 
GlifLayout(Context context, AttributeSet attrs)98   public GlifLayout(Context context, AttributeSet attrs) {
99     super(context, attrs);
100     init(attrs, R.attr.sudLayoutTheme);
101   }
102 
103   @TargetApi(VERSION_CODES.HONEYCOMB)
GlifLayout(Context context, AttributeSet attrs, int defStyleAttr)104   public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) {
105     super(context, attrs, defStyleAttr);
106     init(attrs, defStyleAttr);
107   }
108 
109   // All the constructors delegate to this init method. The 3-argument constructor is not
110   // available in LinearLayout before v11, so call super with the exact same arguments.
init(AttributeSet attrs, int defStyleAttr)111   private void init(AttributeSet attrs, int defStyleAttr) {
112     if (isInEditMode()) {
113       return;
114     }
115 
116     TypedArray a =
117         getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0);
118     boolean usePartnerHeavyTheme =
119         a.getBoolean(R.styleable.SudGlifLayout_sudUsePartnerHeavyTheme, false);
120     applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme;
121 
122     registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
123     registerMixin(DescriptionMixin.class, new DescriptionMixin(this, attrs, defStyleAttr));
124     registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
125     registerMixin(ProfileMixin.class, new ProfileMixin(this, attrs, defStyleAttr));
126     registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this, attrs, defStyleAttr));
127     registerMixin(IllustrationProgressMixin.class, new IllustrationProgressMixin(this));
128     final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
129     registerMixin(RequireScrollMixin.class, requireScrollMixin);
130 
131     final ScrollView scrollView = getScrollView();
132     if (scrollView != null) {
133       requireScrollMixin.setScrollHandlingDelegate(
134           new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
135     }
136 
137     ColorStateList primaryColor = a.getColorStateList(R.styleable.SudGlifLayout_sudColorPrimary);
138     if (primaryColor != null) {
139       setPrimaryColor(primaryColor);
140     }
141     if (shouldApplyPartnerHeavyThemeResource()) {
142       updateContentBackgroundColorWithPartnerConfig();
143     }
144 
145     View view = findManagedViewById(R.id.sud_layout_content);
146     if (view != null) {
147       if (shouldApplyPartnerResource()) {
148         // The margin of content is defined by @style/SudContentFrame. The Setupdesign library
149         // cannot obtain the content resource ID of the client, so the value of the content margin
150         // cannot be adjusted through GlifLayout. If the margin sides are changed through the
151         // partner config, it can only be based on the increased or decreased value to adjust the
152         // value of pading. In this way, the value of content margin plus padding will be equal to
153         // the value of partner config.
154         LayoutStyler.applyPartnerCustomizationExtraPaddingStyle(view);
155       }
156 
157       // {@class GlifPreferenceLayout} Inherited from {@class GlifRecyclerLayout}. The API would
158       // be called twice from GlifRecyclerLayout and GlifLayout, so it should skip the API here
159       // when the instance is GlifPreferenceLayout.
160       if (!(this instanceof GlifPreferenceLayout)) {
161         tryApplyPartnerCustomizationContentPaddingTopStyle(view);
162       }
163     }
164 
165     updateLandscapeMiddleHorizontalSpacing();
166 
167     ColorStateList backgroundColor =
168         a.getColorStateList(R.styleable.SudGlifLayout_sudBackgroundBaseColor);
169     setBackgroundBaseColor(backgroundColor);
170 
171     boolean backgroundPatterned =
172         a.getBoolean(R.styleable.SudGlifLayout_sudBackgroundPatterned, true);
173     setBackgroundPatterned(backgroundPatterned);
174 
175     final int stickyHeader = a.getResourceId(R.styleable.SudGlifLayout_sudStickyHeader, 0);
176     if (stickyHeader != 0) {
177       inflateStickyHeader(stickyHeader);
178     }
179     a.recycle();
180   }
181 
182   @Override
onFinishInflate()183   protected void onFinishInflate() {
184     super.onFinishInflate();
185     getMixin(IconMixin.class).tryApplyPartnerCustomizationStyle();
186     getMixin(HeaderMixin.class).tryApplyPartnerCustomizationStyle();
187     getMixin(DescriptionMixin.class).tryApplyPartnerCustomizationStyle();
188     getMixin(ProgressBarMixin.class).tryApplyPartnerCustomizationStyle();
189     getMixin(ProfileMixin.class).tryApplyPartnerCustomizationStyle();
190     tryApplyPartnerCustomizationStyleToShortDescription();
191   }
192 
193   // TODO: remove when all sud_layout_description has migrated to
194   // DescriptionMixin(sud_layout_subtitle)
tryApplyPartnerCustomizationStyleToShortDescription()195   private void tryApplyPartnerCustomizationStyleToShortDescription() {
196     TextView description = this.findManagedViewById(R.id.sud_layout_description);
197     if (description != null) {
198       if (applyPartnerHeavyThemeResource) {
199         DescriptionStyler.applyPartnerCustomizationHeavyStyle(description);
200       } else if (shouldApplyPartnerResource()) {
201         DescriptionStyler.applyPartnerCustomizationLightStyle(description);
202       }
203     }
204   }
205 
updateLandscapeMiddleHorizontalSpacing()206   protected void updateLandscapeMiddleHorizontalSpacing() {
207     int horizontalSpacing =
208         getResources().getDimensionPixelSize(R.dimen.sud_glif_land_middle_horizontal_spacing);
209     if (shouldApplyPartnerResource()
210         && PartnerConfigHelper.get(getContext())
211             .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAND_MIDDLE_HORIZONTAL_SPACING)) {
212       horizontalSpacing =
213           (int)
214               PartnerConfigHelper.get(getContext())
215                   .getDimension(getContext(), PartnerConfig.CONFIG_LAND_MIDDLE_HORIZONTAL_SPACING);
216     }
217 
218     View headerView = this.findManagedViewById(R.id.sud_landscape_header_area);
219     if (headerView != null) {
220       int layoutMarginEnd;
221       if (shouldApplyPartnerResource()
222           && PartnerConfigHelper.get(getContext())
223               .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_END)) {
224         layoutMarginEnd =
225             (int)
226                 PartnerConfigHelper.get(getContext())
227                     .getDimension(getContext(), PartnerConfig.CONFIG_LAYOUT_MARGIN_END);
228       } else {
229         TypedArray a = getContext().obtainStyledAttributes(new int[] {R.attr.sudMarginEnd});
230         layoutMarginEnd = a.getDimensionPixelSize(0, 0);
231         a.recycle();
232       }
233       int paddingEnd = (horizontalSpacing / 2) - layoutMarginEnd;
234       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
235         headerView.setPadding(
236             headerView.getPaddingStart(),
237             headerView.getPaddingTop(),
238             paddingEnd,
239             headerView.getPaddingBottom());
240       } else {
241         headerView.setPadding(
242             headerView.getPaddingLeft(),
243             headerView.getPaddingTop(),
244             paddingEnd,
245             headerView.getPaddingBottom());
246       }
247     }
248 
249     View contentView = this.findManagedViewById(R.id.sud_landscape_content_area);
250     if (contentView != null) {
251       int layoutMarginStart;
252       if (shouldApplyPartnerResource()
253           && PartnerConfigHelper.get(getContext())
254               .isPartnerConfigAvailable(PartnerConfig.CONFIG_LAYOUT_MARGIN_START)) {
255         layoutMarginStart =
256             (int)
257                 PartnerConfigHelper.get(getContext())
258                     .getDimension(getContext(), PartnerConfig.CONFIG_LAYOUT_MARGIN_START);
259       } else {
260         TypedArray a = getContext().obtainStyledAttributes(new int[] {R.attr.sudMarginStart});
261         layoutMarginStart = a.getDimensionPixelSize(0, 0);
262         a.recycle();
263       }
264       int paddingStart = 0;
265       if (headerView != null) {
266         paddingStart = (horizontalSpacing / 2) - layoutMarginStart;
267       }
268       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
269         contentView.setPadding(
270             paddingStart,
271             contentView.getPaddingTop(),
272             contentView.getPaddingEnd(),
273             contentView.getPaddingBottom());
274       } else {
275         contentView.setPadding(
276             paddingStart,
277             contentView.getPaddingTop(),
278             contentView.getPaddingRight(),
279             contentView.getPaddingBottom());
280       }
281     }
282   }
283 
284   @Override
onInflateTemplate(LayoutInflater inflater, @LayoutRes int template)285   protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
286     if (template == 0) {
287       template = R.layout.sud_glif_template;
288     }
289     return inflateTemplate(inflater, R.style.SudThemeGlif_Light, template);
290   }
291 
292   @Override
findContainer(int containerId)293   protected ViewGroup findContainer(int containerId) {
294     if (containerId == 0) {
295       containerId = R.id.sud_layout_content;
296     }
297     return super.findContainer(containerId);
298   }
299 
300   /**
301    * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top of
302    * the content area outside of the scrolling container. The header can only be inflated once per
303    * instance of this layout.
304    *
305    * @param header The layout to be inflated as the header
306    * @return The root of the inflated header view
307    */
inflateStickyHeader(@ayoutRes int header)308   public View inflateStickyHeader(@LayoutRes int header) {
309     ViewStub stickyHeaderStub = findManagedViewById(R.id.sud_layout_sticky_header);
310     stickyHeaderStub.setLayoutResource(header);
311     return stickyHeaderStub.inflate();
312   }
313 
getScrollView()314   public ScrollView getScrollView() {
315     final View view = findManagedViewById(R.id.sud_scroll_view);
316     return view instanceof ScrollView ? (ScrollView) view : null;
317   }
318 
getHeaderTextView()319   public TextView getHeaderTextView() {
320     return getMixin(HeaderMixin.class).getTextView();
321   }
322 
setHeaderText(int title)323   public void setHeaderText(int title) {
324     getMixin(HeaderMixin.class).setText(title);
325   }
326 
setHeaderText(CharSequence title)327   public void setHeaderText(CharSequence title) {
328     getMixin(HeaderMixin.class).setText(title);
329   }
330 
getHeaderText()331   public CharSequence getHeaderText() {
332     return getMixin(HeaderMixin.class).getText();
333   }
334 
getDescriptionTextView()335   public TextView getDescriptionTextView() {
336     return getMixin(DescriptionMixin.class).getTextView();
337   }
338 
339   /**
340    * Sets the description text and also sets the text visibility to visible. This can also be set
341    * via the XML attribute {@code app:sudDescriptionText}.
342    *
343    * @param title The resource ID of the text to be set as description
344    */
setDescriptionText(@tringRes int title)345   public void setDescriptionText(@StringRes int title) {
346     getMixin(DescriptionMixin.class).setText(title);
347   }
348 
349   /**
350    * Sets the description text and also sets the text visibility to visible. This can also be set
351    * via the XML attribute {@code app:sudDescriptionText}.
352    *
353    * @param title The text to be set as description
354    */
setDescriptionText(CharSequence title)355   public void setDescriptionText(CharSequence title) {
356     getMixin(DescriptionMixin.class).setText(title);
357   }
358 
359   /** Returns the current description text. */
getDescriptionText()360   public CharSequence getDescriptionText() {
361     return getMixin(DescriptionMixin.class).getText();
362   }
363 
setHeaderColor(ColorStateList color)364   public void setHeaderColor(ColorStateList color) {
365     getMixin(HeaderMixin.class).setTextColor(color);
366   }
367 
getHeaderColor()368   public ColorStateList getHeaderColor() {
369     return getMixin(HeaderMixin.class).getTextColor();
370   }
371 
setIcon(Drawable icon)372   public void setIcon(Drawable icon) {
373     getMixin(IconMixin.class).setIcon(icon);
374   }
375 
getIcon()376   public Drawable getIcon() {
377     return getMixin(IconMixin.class).getIcon();
378   }
379 
380   /**
381    * Sets the visibility of header area in landscape mode. These views inlcudes icon, header title
382    * and subtitle. It can make the content view become full screen when set false.
383    */
384   @TargetApi(Build.VERSION_CODES.S)
setLandscapeHeaderAreaVisible(boolean visible)385   public void setLandscapeHeaderAreaVisible(boolean visible) {
386     View view = this.findManagedViewById(R.id.sud_landscape_header_area);
387     if (view == null) {
388       return;
389     }
390     if (visible) {
391       view.setVisibility(View.VISIBLE);
392     } else {
393       view.setVisibility(View.GONE);
394     }
395     updateLandscapeMiddleHorizontalSpacing();
396   }
397 
398   /**
399    * Sets the primary color of this layout, which will be used to determine the color of the
400    * progress bar and the background pattern.
401    */
setPrimaryColor(@onNull ColorStateList color)402   public void setPrimaryColor(@NonNull ColorStateList color) {
403     primaryColor = color;
404     updateBackground();
405     getMixin(ProgressBarMixin.class).setColor(color);
406   }
407 
getPrimaryColor()408   public ColorStateList getPrimaryColor() {
409     return primaryColor;
410   }
411 
412   /**
413    * Sets the base color of the background view, which is the status bar for phones and the full-
414    * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will be
415    * drawn with this color.
416    *
417    * @param color The color to use as the base color of the background. If {@code null}, {@link
418    *     #getPrimaryColor()} will be used
419    */
setBackgroundBaseColor(@ullable ColorStateList color)420   public void setBackgroundBaseColor(@Nullable ColorStateList color) {
421     backgroundBaseColor = color;
422     updateBackground();
423   }
424 
425   /**
426    * @return The base color of the background. {@code null} indicates the background will be drawn
427    *     with {@link #getPrimaryColor()}.
428    */
429   @Nullable
getBackgroundBaseColor()430   public ColorStateList getBackgroundBaseColor() {
431     return backgroundBaseColor;
432   }
433 
434   /**
435    * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the
436    * background will be a solid color.
437    */
setBackgroundPatterned(boolean patterned)438   public void setBackgroundPatterned(boolean patterned) {
439     backgroundPatterned = patterned;
440     updateBackground();
441   }
442 
443   /** @return True if this view uses {@link GlifPatternDrawable} as background. */
isBackgroundPatterned()444   public boolean isBackgroundPatterned() {
445     return backgroundPatterned;
446   }
447 
updateBackground()448   private void updateBackground() {
449     final View patternBg = findManagedViewById(R.id.suc_layout_status);
450     if (patternBg != null) {
451       int backgroundColor = 0;
452       if (backgroundBaseColor != null) {
453         backgroundColor = backgroundBaseColor.getDefaultColor();
454       } else if (primaryColor != null) {
455         backgroundColor = primaryColor.getDefaultColor();
456       }
457       Drawable background =
458           backgroundPatterned
459               ? new GlifPatternDrawable(backgroundColor)
460               : new ColorDrawable(backgroundColor);
461       getMixin(StatusBarMixin.class).setStatusBarBackground(background);
462     }
463   }
464 
isProgressBarShown()465   public boolean isProgressBarShown() {
466     return getMixin(ProgressBarMixin.class).isShown();
467   }
468 
setProgressBarShown(boolean shown)469   public void setProgressBarShown(boolean shown) {
470     getMixin(ProgressBarMixin.class).setShown(shown);
471   }
472 
peekProgressBar()473   public ProgressBar peekProgressBar() {
474     return getMixin(ProgressBarMixin.class).peekProgressBar();
475   }
476 
477   /**
478    * Returns if the current layout/activity applies heavy partner customized configurations or not.
479    */
shouldApplyPartnerHeavyThemeResource()480   public boolean shouldApplyPartnerHeavyThemeResource() {
481 
482     return applyPartnerHeavyThemeResource
483         || (shouldApplyPartnerResource()
484             && PartnerConfigHelper.shouldApplyExtendedPartnerConfig(getContext()));
485   }
486 
487   /** Updates the background color of this layout with the partner-customizable background color. */
updateContentBackgroundColorWithPartnerConfig()488   private void updateContentBackgroundColorWithPartnerConfig() {
489     // If full dynamic color enabled which means this activity is running outside of setup
490     // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
491     if (useFullDynamicColor()) {
492       return;
493     }
494 
495     @ColorInt
496     int color =
497         PartnerConfigHelper.get(getContext())
498             .getColor(getContext(), PartnerConfig.CONFIG_LAYOUT_BACKGROUND_COLOR);
499     this.getRootView().setBackgroundColor(color);
500   }
501 
502   @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
tryApplyPartnerCustomizationContentPaddingTopStyle(View view)503   protected void tryApplyPartnerCustomizationContentPaddingTopStyle(View view) {
504     Context context = view.getContext();
505     boolean partnerPaddingTopAvailable =
506         PartnerConfigHelper.get(context)
507             .isPartnerConfigAvailable(PartnerConfig.CONFIG_CONTENT_PADDING_TOP);
508 
509     if (shouldApplyPartnerResource() && partnerPaddingTopAvailable) {
510       int paddingTop =
511           (int)
512               PartnerConfigHelper.get(context)
513                   .getDimension(context, PartnerConfig.CONFIG_CONTENT_PADDING_TOP);
514 
515       if (paddingTop != view.getPaddingTop()) {
516         view.setPadding(
517             view.getPaddingStart(), paddingTop, view.getPaddingEnd(), view.getPaddingBottom());
518       }
519     }
520   }
521 }
522