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.VERSION_CODES;
26 import androidx.annotation.ColorInt;
27 import androidx.annotation.LayoutRes;
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import android.util.AttributeSet;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewStub;
35 import android.widget.ProgressBar;
36 import android.widget.ScrollView;
37 import android.widget.TextView;
38 import com.google.android.setupcompat.PartnerCustomizationLayout;
39 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
40 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
41 import com.google.android.setupcompat.template.StatusBarMixin;
42 import com.google.android.setupdesign.template.HeaderMixin;
43 import com.google.android.setupdesign.template.IconMixin;
44 import com.google.android.setupdesign.template.ProgressBarMixin;
45 import com.google.android.setupdesign.template.RequireScrollMixin;
46 import com.google.android.setupdesign.template.ScrollViewScrollHandlingDelegate;
47 import com.google.android.setupdesign.util.DescriptionStyler;
48 
49 /**
50  * Layout for the GLIF theme used in Setup Wizard for N.
51  *
52  * <p>Example usage:
53  *
54  * <pre>{@code
55  * &lt;com.google.android.setupdesign.GlifLayout
56  *     xmlns:android="http://schemas.android.com/apk/res/android"
57  *     xmlns:app="http://schemas.android.com/apk/res-auto"
58  *     android:layout_width="match_parent"
59  *     android:layout_height="match_parent"
60  *     android:icon="@drawable/my_icon"
61  *     app:sucHeaderText="@string/my_title">
62  *
63  *     &lt;!-- Content here -->
64  *
65  * &lt;/com.google.android.setupdesign.GlifLayout>
66  * }</pre>
67  */
68 public class GlifLayout extends PartnerCustomizationLayout {
69 
70   private static final String TAG = "GlifLayout";
71 
72   private ColorStateList primaryColor;
73 
74   private boolean backgroundPatterned = true;
75 
76   private boolean applyPartnerHeavyThemeResource = false;
77 
78   /** The color of the background. If null, the color will inherit from primaryColor. */
79   @Nullable private ColorStateList backgroundBaseColor;
80 
GlifLayout(Context context)81   public GlifLayout(Context context) {
82     this(context, 0, 0);
83   }
84 
GlifLayout(Context context, int template)85   public GlifLayout(Context context, int template) {
86     this(context, template, 0);
87   }
88 
GlifLayout(Context context, int template, int containerId)89   public GlifLayout(Context context, int template, int containerId) {
90     super(context, template, containerId);
91     init(null, R.attr.sudLayoutTheme);
92   }
93 
GlifLayout(Context context, AttributeSet attrs)94   public GlifLayout(Context context, AttributeSet attrs) {
95     super(context, attrs);
96     init(attrs, R.attr.sudLayoutTheme);
97   }
98 
99   @TargetApi(VERSION_CODES.HONEYCOMB)
GlifLayout(Context context, AttributeSet attrs, int defStyleAttr)100   public GlifLayout(Context context, AttributeSet attrs, int defStyleAttr) {
101     super(context, attrs, defStyleAttr);
102     init(attrs, defStyleAttr);
103   }
104 
105   // All the constructors delegate to this init method. The 3-argument constructor is not
106   // available in LinearLayout before v11, so call super with the exact same arguments.
init(AttributeSet attrs, int defStyleAttr)107   private void init(AttributeSet attrs, int defStyleAttr) {
108 
109     TypedArray a =
110         getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0);
111     boolean usePartnerHeavyTheme =
112         a.getBoolean(R.styleable.SudGlifLayout_sudUsePartnerHeavyTheme, false);
113     applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme;
114 
115     registerMixin(
116         HeaderMixin.class,
117         new HeaderMixin(this, attrs, defStyleAttr));
118     registerMixin(
119         IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
120     registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
121     final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
122     registerMixin(RequireScrollMixin.class, requireScrollMixin);
123 
124     final ScrollView scrollView = getScrollView();
125     if (scrollView != null) {
126       requireScrollMixin.setScrollHandlingDelegate(
127           new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
128     }
129 
130     ColorStateList primaryColor = a.getColorStateList(R.styleable.SudGlifLayout_sudColorPrimary);
131     if (primaryColor != null) {
132       setPrimaryColor(primaryColor);
133     }
134 
135     if (applyPartnerHeavyThemeResource) {
136       updateContentBackgroundColorWithPartnerConfig();
137     }
138 
139     ColorStateList backgroundColor =
140         a.getColorStateList(R.styleable.SudGlifLayout_sudBackgroundBaseColor);
141     setBackgroundBaseColor(backgroundColor);
142 
143     boolean backgroundPatterned =
144         a.getBoolean(R.styleable.SudGlifLayout_sudBackgroundPatterned, true);
145     setBackgroundPatterned(backgroundPatterned);
146 
147     final int stickyHeader = a.getResourceId(R.styleable.SudGlifLayout_sudStickyHeader, 0);
148     if (stickyHeader != 0) {
149       inflateStickyHeader(stickyHeader);
150     }
151     a.recycle();
152   }
153 
154   @Override
onFinishInflate()155   protected void onFinishInflate() {
156     super.onFinishInflate();
157 
158     TextView description = this.findManagedViewById(R.id.sud_layout_description);
159     if (description != null) {
160       if (applyPartnerHeavyThemeResource) {
161         DescriptionStyler.applyPartnerCustomizationStyle(description);
162       }
163     }
164     getMixin(IconMixin.class).applyPartnerCustomizationStyle();
165     getMixin(HeaderMixin.class).applyPartnerCustomizationStyle();
166   }
167 
168   @Override
onInflateTemplate(LayoutInflater inflater, @LayoutRes int template)169   protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
170     if (template == 0) {
171       template = R.layout.sud_glif_template;
172     }
173     return inflateTemplate(inflater, R.style.SudThemeGlif_Light, template);
174   }
175 
176   @Override
findContainer(int containerId)177   protected ViewGroup findContainer(int containerId) {
178     if (containerId == 0) {
179       containerId = R.id.sud_layout_content;
180     }
181     return super.findContainer(containerId);
182   }
183 
184   /**
185    * Sets the sticky header (i.e. header that doesn't scroll) of the layout, which is at the top of
186    * the content area outside of the scrolling container. The header can only be inflated once per
187    * instance of this layout.
188    *
189    * @param header The layout to be inflated as the header.
190    * @return The root of the inflated header view.
191    */
inflateStickyHeader(@ayoutRes int header)192   public View inflateStickyHeader(@LayoutRes int header) {
193     ViewStub stickyHeaderStub = findManagedViewById(R.id.sud_layout_sticky_header);
194     stickyHeaderStub.setLayoutResource(header);
195     return stickyHeaderStub.inflate();
196   }
197 
getScrollView()198   public ScrollView getScrollView() {
199     final View view = findManagedViewById(R.id.sud_scroll_view);
200     return view instanceof ScrollView ? (ScrollView) view : null;
201   }
202 
getHeaderTextView()203   public TextView getHeaderTextView() {
204     return getMixin(HeaderMixin.class).getTextView();
205   }
206 
setHeaderText(int title)207   public void setHeaderText(int title) {
208     getMixin(HeaderMixin.class).setText(title);
209   }
210 
setHeaderText(CharSequence title)211   public void setHeaderText(CharSequence title) {
212     getMixin(HeaderMixin.class).setText(title);
213   }
214 
getHeaderText()215   public CharSequence getHeaderText() {
216     return getMixin(HeaderMixin.class).getText();
217   }
218 
setHeaderColor(ColorStateList color)219   public void setHeaderColor(ColorStateList color) {
220     getMixin(HeaderMixin.class).setTextColor(color);
221   }
222 
getHeaderColor()223   public ColorStateList getHeaderColor() {
224     return getMixin(HeaderMixin.class).getTextColor();
225   }
226 
setIcon(Drawable icon)227   public void setIcon(Drawable icon) {
228     getMixin(IconMixin.class).setIcon(icon);
229   }
230 
getIcon()231   public Drawable getIcon() {
232     return getMixin(IconMixin.class).getIcon();
233   }
234 
235   /**
236    * Sets the primary color of this layout, which will be used to determine the color of the
237    * progress bar and the background pattern.
238    */
setPrimaryColor(@onNull ColorStateList color)239   public void setPrimaryColor(@NonNull ColorStateList color) {
240     primaryColor = color;
241     updateBackground();
242     getMixin(ProgressBarMixin.class).setColor(color);
243   }
244 
getPrimaryColor()245   public ColorStateList getPrimaryColor() {
246     return primaryColor;
247   }
248 
249   /**
250    * Sets the base color of the background view, which is the status bar for phones and the full-
251    * screen background for tablets. If {@link #isBackgroundPatterned()} is true, the pattern will be
252    * drawn with this color.
253    *
254    * @param color The color to use as the base color of the background. If {@code null}, {@link
255    *     #getPrimaryColor()} will be used.
256    */
setBackgroundBaseColor(@ullable ColorStateList color)257   public void setBackgroundBaseColor(@Nullable ColorStateList color) {
258     backgroundBaseColor = color;
259     updateBackground();
260   }
261 
262   /**
263    * @return The base color of the background. {@code null} indicates the background will be drawn
264    *     with {@link #getPrimaryColor()}.
265    */
266   @Nullable
getBackgroundBaseColor()267   public ColorStateList getBackgroundBaseColor() {
268     return backgroundBaseColor;
269   }
270 
271   /**
272    * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the
273    * background will be a solid color.
274    */
setBackgroundPatterned(boolean patterned)275   public void setBackgroundPatterned(boolean patterned) {
276     backgroundPatterned = patterned;
277     updateBackground();
278   }
279 
280   /** @return True if this view uses {@link GlifPatternDrawable} as background. */
isBackgroundPatterned()281   public boolean isBackgroundPatterned() {
282     return backgroundPatterned;
283   }
284 
updateBackground()285   private void updateBackground() {
286     final View patternBg = findManagedViewById(R.id.suc_layout_status);
287     if (patternBg != null) {
288       int backgroundColor = 0;
289       if (backgroundBaseColor != null) {
290         backgroundColor = backgroundBaseColor.getDefaultColor();
291       } else if (primaryColor != null) {
292         backgroundColor = primaryColor.getDefaultColor();
293       }
294       Drawable background =
295           backgroundPatterned
296               ? new GlifPatternDrawable(backgroundColor)
297               : new ColorDrawable(backgroundColor);
298       getMixin(StatusBarMixin.class).setStatusBarBackground(background);
299     }
300   }
301 
isProgressBarShown()302   public boolean isProgressBarShown() {
303     return getMixin(ProgressBarMixin.class).isShown();
304   }
305 
setProgressBarShown(boolean shown)306   public void setProgressBarShown(boolean shown) {
307     getMixin(ProgressBarMixin.class).setShown(shown);
308   }
309 
peekProgressBar()310   public ProgressBar peekProgressBar() {
311     return getMixin(ProgressBarMixin.class).peekProgressBar();
312   }
313 
314   /**
315    * Returns if the current layout/activity applies heavy partner customized configurations or not.
316    */
shouldApplyPartnerHeavyThemeResource()317   public boolean shouldApplyPartnerHeavyThemeResource() {
318     return applyPartnerHeavyThemeResource;
319   }
320 
321   /** Updates the background color of this layout with the partner-customizable background color. */
updateContentBackgroundColorWithPartnerConfig()322   private void updateContentBackgroundColorWithPartnerConfig() {
323     @ColorInt
324     int color =
325         PartnerConfigHelper.get(getContext())
326             .getColor(getContext(), PartnerConfig.CONFIG_LAYOUT_BACKGROUND_COLOR);
327     this.getRootView().setBackgroundColor(color);
328   }
329 }
330