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