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 
17 package com.google.android.setupcompat;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.res.TypedArray;
24 import android.os.Build;
25 import android.os.Build.VERSION;
26 import android.os.Build.VERSION_CODES;
27 import android.os.PersistableBundle;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.WindowManager;
33 import com.google.android.setupcompat.internal.LifecycleFragment;
34 import com.google.android.setupcompat.internal.PersistableBundles;
35 import com.google.android.setupcompat.internal.TemplateLayout;
36 import com.google.android.setupcompat.logging.CustomEvent;
37 import com.google.android.setupcompat.logging.MetricKey;
38 import com.google.android.setupcompat.logging.SetupMetricsLogger;
39 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
40 import com.google.android.setupcompat.template.FooterBarMixin;
41 import com.google.android.setupcompat.template.FooterButton;
42 import com.google.android.setupcompat.template.StatusBarMixin;
43 import com.google.android.setupcompat.template.SystemNavBarMixin;
44 import com.google.android.setupcompat.util.BuildCompatUtils;
45 import com.google.android.setupcompat.util.Logger;
46 import com.google.android.setupcompat.util.WizardManagerHelper;
47 
48 /** A templatization layout with consistent style used in Setup Wizard or app itself. */
49 public class PartnerCustomizationLayout extends TemplateLayout {
50 
51   private static final Logger LOG = new Logger("PartnerCustomizationLayout");
52 
53   /**
54    * Attribute indicating whether usage of partner theme resources is allowed. This corresponds to
55    * the {@code app:sucUsePartnerResource} XML attribute. Note that when running in setup wizard,
56    * this is always overridden to true.
57    */
58   private boolean usePartnerResourceAttr;
59 
60   /**
61    * Attribute indicating whether using full dynamic colors or not. This corresponds to the {@code
62    * app:sucFullDynamicColor} XML attribute.
63    */
64   private boolean useFullDynamicColorAttr;
65 
66   /**
67    * Attribute indicating whether usage of dynamic is allowed. This corresponds to the existence of
68    * {@code app:sucFullDynamicColor} XML attribute.
69    */
70   private boolean useDynamicColor;
71 
72   private Activity activity;
73 
PartnerCustomizationLayout(Context context)74   public PartnerCustomizationLayout(Context context) {
75     this(context, 0, 0);
76   }
77 
PartnerCustomizationLayout(Context context, int template)78   public PartnerCustomizationLayout(Context context, int template) {
79     this(context, template, 0);
80   }
81 
PartnerCustomizationLayout(Context context, int template, int containerId)82   public PartnerCustomizationLayout(Context context, int template, int containerId) {
83     super(context, template, containerId);
84     init(null, R.attr.sucLayoutTheme);
85   }
86 
PartnerCustomizationLayout(Context context, AttributeSet attrs)87   public PartnerCustomizationLayout(Context context, AttributeSet attrs) {
88     super(context, attrs);
89     init(attrs, R.attr.sucLayoutTheme);
90   }
91 
92   @TargetApi(VERSION_CODES.HONEYCOMB)
PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr)93   public PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr) {
94     super(context, attrs, defStyleAttr);
95     init(attrs, defStyleAttr);
96   }
97 
init(AttributeSet attrs, int defStyleAttr)98   private void init(AttributeSet attrs, int defStyleAttr) {
99     if (isInEditMode()) {
100       return;
101     }
102 
103     TypedArray a =
104         getContext()
105             .obtainStyledAttributes(
106                 attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
107 
108     boolean layoutFullscreen =
109         a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucLayoutFullscreen, true);
110 
111     a.recycle();
112 
113     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) {
114       setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
115     }
116 
117     registerMixin(
118         StatusBarMixin.class, new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr));
119     registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, activity.getWindow()));
120     registerMixin(FooterBarMixin.class, new FooterBarMixin(this, attrs, defStyleAttr));
121 
122     getMixin(SystemNavBarMixin.class).applyPartnerCustomizations(attrs, defStyleAttr);
123 
124     // Override the FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_TRANSLUCENT_STATUS,
125     // FLAG_TRANSLUCENT_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attributes of window forces
126     // showing status bar and navigation bar.
127     if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
128       activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
129       activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
130       activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
131     }
132   }
133 
134   @Override
onInflateTemplate(LayoutInflater inflater, int template)135   protected View onInflateTemplate(LayoutInflater inflater, int template) {
136     if (template == 0) {
137       template = R.layout.partner_customization_layout;
138     }
139     return inflateTemplate(inflater, 0, template);
140   }
141 
142   /**
143    * {@inheritDoc}
144    *
145    * <p>This method sets all these flags before onTemplateInflated since it will be too late and get
146    * incorrect flag value on PartnerCustomizationLayout if sets them after onTemplateInflated.
147    */
148   @Override
onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr)149   protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {
150 
151     // Sets default value to true since this timing
152     // before PartnerCustomization members initialization
153     usePartnerResourceAttr = true;
154 
155     activity = lookupActivityFromContext(getContext());
156 
157     boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
158 
159     TypedArray a =
160         getContext()
161             .obtainStyledAttributes(
162                 attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
163 
164     if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) {
165       // TODO: Enable Log.WTF after other client already set sucUsePartnerResource.
166       LOG.e("Attribute sucUsePartnerResource not found in " + activity.getComponentName());
167     }
168 
169     usePartnerResourceAttr =
170         isSetupFlow
171             || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true);
172 
173     useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor);
174     useFullDynamicColorAttr =
175         a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false);
176 
177     a.recycle();
178 
179     LOG.atDebug(
180         "activity="
181             + activity.getClass().getSimpleName()
182             + " isSetupFlow="
183             + isSetupFlow
184             + " enablePartnerResourceLoading="
185             + enablePartnerResourceLoading()
186             + " usePartnerResourceAttr="
187             + usePartnerResourceAttr
188             + " useDynamicColor="
189             + useDynamicColor
190             + " useFullDynamicColorAttr="
191             + useFullDynamicColorAttr);
192   }
193 
194   @Override
findContainer(int containerId)195   protected ViewGroup findContainer(int containerId) {
196     if (containerId == 0) {
197       containerId = R.id.suc_layout_content;
198     }
199     return super.findContainer(containerId);
200   }
201 
202   @Override
onAttachedToWindow()203   protected void onAttachedToWindow() {
204     super.onAttachedToWindow();
205     LifecycleFragment.attachNow(activity);
206     getMixin(FooterBarMixin.class).onAttachedToWindow();
207   }
208 
209   @Override
onDetachedFromWindow()210   protected void onDetachedFromWindow() {
211     super.onDetachedFromWindow();
212     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
213         && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
214         && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
215       FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class);
216       footerBarMixin.onDetachedFromWindow();
217       FooterButton primaryButton = footerBarMixin.getPrimaryButton();
218       FooterButton secondaryButton = footerBarMixin.getSecondaryButton();
219       PersistableBundle primaryButtonMetrics =
220           primaryButton != null
221               ? primaryButton.getMetrics("PrimaryFooterButton")
222               : PersistableBundle.EMPTY;
223       PersistableBundle secondaryButtonMetrics =
224           secondaryButton != null
225               ? secondaryButton.getMetrics("SecondaryFooterButton")
226               : PersistableBundle.EMPTY;
227 
228       PersistableBundle persistableBundle =
229           PersistableBundles.mergeBundles(
230               footerBarMixin.getLoggingMetrics(), primaryButtonMetrics, secondaryButtonMetrics);
231 
232       SetupMetricsLogger.logCustomEvent(
233           getContext(),
234           CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle));
235     }
236   }
237 
lookupActivityFromContext(Context context)238   public static Activity lookupActivityFromContext(Context context) {
239     if (context instanceof Activity) {
240       return (Activity) context;
241     } else if (context instanceof ContextWrapper) {
242       return lookupActivityFromContext(((ContextWrapper) context).getBaseContext());
243     } else {
244       throw new IllegalArgumentException("Cannot find instance of Activity in parent tree");
245     }
246   }
247 
248   /**
249    * Returns true if partner resource loading is enabled. If true, and other necessary conditions
250    * for loading theme attributes are met, this layout will use customized theme attributes from OEM
251    * overlays. This is intended to be used with flag-based development, to allow a flag to control
252    * the rollout of partner resource loading.
253    */
enablePartnerResourceLoading()254   protected boolean enablePartnerResourceLoading() {
255     return true;
256   }
257 
258   /** Returns if the current layout/activity applies partner customized configurations or not. */
shouldApplyPartnerResource()259   public boolean shouldApplyPartnerResource() {
260     if (!enablePartnerResourceLoading()) {
261       return false;
262     }
263     if (!usePartnerResourceAttr) {
264       return false;
265     }
266     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
267       return false;
268     }
269     if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
270       return false;
271     }
272     return true;
273   }
274 
275   /**
276    * Returns {@code true} if the current layout/activity applies dynamic color. Otherwise, returns
277    * {@code false}.
278    */
shouldApplyDynamicColor()279   public boolean shouldApplyDynamicColor() {
280     if (!useDynamicColor) {
281       return false;
282     }
283     if (!BuildCompatUtils.isAtLeastS()) {
284       return false;
285     }
286     if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
287       return false;
288     }
289     return true;
290   }
291 
292   /**
293    * Returns {@code true} if the current layout/activity applies full dynamic color. Otherwise,
294    * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()}
295    * and the value of the {@code app:sucFullDynamicColor}.
296    */
useFullDynamicColor()297   public boolean useFullDynamicColor() {
298     return shouldApplyDynamicColor() && useFullDynamicColorAttr;
299   }
300 }
301