blob: 26e7042ec3d9d9f1ca3cd40deb92a1593a64a6bc [file] [log] [blame]
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +08001/*
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
17package com.google.android.setupcompat;
18
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080019import android.app.Activity;
20import android.content.Context;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080021import android.content.res.TypedArray;
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +080022import android.os.Build;
Setup Wizard Team1ed30732019-03-14 10:00:10 +080023import android.os.Build.VERSION;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080024import android.os.Build.VERSION_CODES;
Setup Wizard Team1d79d002018-12-13 14:30:13 +080025import android.os.PersistableBundle;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080026import android.util.AttributeSet;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
Setup Wizard Team55addcd2022-01-06 23:50:39 +080030import android.view.ViewTreeObserver;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080031import android.view.WindowManager;
Setup Wizard Team55addcd2022-01-06 23:50:39 +080032import androidx.annotation.VisibleForTesting;
33import com.google.android.setupcompat.internal.FocusChangedMetricHelper;
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +080034import com.google.android.setupcompat.internal.LifecycleFragment;
Setup Wizard Teamd6fc4af2019-12-31 20:58:11 +080035import com.google.android.setupcompat.internal.PersistableBundles;
Setup Wizard Team55addcd2022-01-06 23:50:39 +080036import com.google.android.setupcompat.internal.SetupCompatServiceInvoker;
Maurice Lamc4529322019-02-14 22:04:56 +000037import com.google.android.setupcompat.internal.TemplateLayout;
Setup Wizard Team1d79d002018-12-13 14:30:13 +080038import com.google.android.setupcompat.logging.CustomEvent;
Setup Wizard Team4f5bbf42023-12-20 03:05:46 +000039import com.google.android.setupcompat.logging.LoggingObserver;
40import com.google.android.setupcompat.logging.LoggingObserver.SetupCompatUiEvent.LayoutInflatedEvent;
Setup Wizard Team1d79d002018-12-13 14:30:13 +080041import com.google.android.setupcompat.logging.MetricKey;
42import com.google.android.setupcompat.logging.SetupMetricsLogger;
Setup Wizard Team1ed30732019-03-14 10:00:10 +080043import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +080044import com.google.android.setupcompat.template.FooterBarMixin;
45import com.google.android.setupcompat.template.FooterButton;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080046import com.google.android.setupcompat.template.StatusBarMixin;
47import com.google.android.setupcompat.template.SystemNavBarMixin;
Setup Wizard Team8a37aa82021-04-26 10:01:19 +080048import com.google.android.setupcompat.util.BuildCompatUtils;
Setup Wizard Teamf513dd22021-06-10 15:49:36 +080049import com.google.android.setupcompat.util.Logger;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080050import com.google.android.setupcompat.util.WizardManagerHelper;
Setup Wizard Team0d3126a2022-04-27 09:03:55 +080051import com.google.errorprone.annotations.CanIgnoreReturnValue;
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080052
53/** A templatization layout with consistent style used in Setup Wizard or app itself. */
54public class PartnerCustomizationLayout extends TemplateLayout {
Setup Wizard Teamf513dd22021-06-10 15:49:36 +080055
56 private static final Logger LOG = new Logger("PartnerCustomizationLayout");
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080057
Setup Wizard Team67e7fef2019-03-23 08:06:48 +080058 /**
59 * Attribute indicating whether usage of partner theme resources is allowed. This corresponds to
60 * the {@code app:sucUsePartnerResource} XML attribute. Note that when running in setup wizard,
61 * this is always overridden to true.
62 */
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +080063 private boolean usePartnerResourceAttr;
Setup Wizard Team1ed30732019-03-14 10:00:10 +080064
Setup Wizard Team8a37aa82021-04-26 10:01:19 +080065 /**
66 * Attribute indicating whether using full dynamic colors or not. This corresponds to the {@code
67 * app:sucFullDynamicColor} XML attribute.
68 */
69 private boolean useFullDynamicColorAttr;
70
71 /**
72 * Attribute indicating whether usage of dynamic is allowed. This corresponds to the existence of
73 * {@code app:sucFullDynamicColor} XML attribute.
74 */
75 private boolean useDynamicColor;
76
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080077 private Activity activity;
78
David Liu534db6a2023-02-15 06:55:44 +000079 private PersistableBundle layoutTypeBundle;
80
Setup Wizard Team0d3126a2022-04-27 09:03:55 +080081 @CanIgnoreReturnValue
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080082 public PartnerCustomizationLayout(Context context) {
83 this(context, 0, 0);
84 }
85
Setup Wizard Team0d3126a2022-04-27 09:03:55 +080086 @CanIgnoreReturnValue
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080087 public PartnerCustomizationLayout(Context context, int template) {
88 this(context, template, 0);
89 }
90
Setup Wizard Team0d3126a2022-04-27 09:03:55 +080091 @CanIgnoreReturnValue
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080092 public PartnerCustomizationLayout(Context context, int template, int containerId) {
93 super(context, template, containerId);
94 init(null, R.attr.sucLayoutTheme);
95 }
96
Setup Wizard Team0d3126a2022-04-27 09:03:55 +080097 @CanIgnoreReturnValue
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +080098 public PartnerCustomizationLayout(Context context, AttributeSet attrs) {
99 super(context, attrs);
100 init(attrs, R.attr.sucLayoutTheme);
101 }
102
Setup Wizard Team0d3126a2022-04-27 09:03:55 +0800103 @CanIgnoreReturnValue
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800104 public PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr) {
105 super(context, attrs, defStyleAttr);
106 init(attrs, defStyleAttr);
107 }
108
David Liu534db6a2023-02-15 06:55:44 +0000109 @VisibleForTesting
110 final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener =
111 this::onFocusChanged;
112
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800113 private void init(AttributeSet attrs, int defStyleAttr) {
Setup Wizard Team261f8b42021-03-04 09:18:37 +0800114 if (isInEditMode()) {
115 return;
116 }
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800117
118 TypedArray a =
119 getContext()
120 .obtainStyledAttributes(
121 attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
122
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800123 boolean layoutFullscreen =
124 a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucLayoutFullscreen, true);
Setup Wizard Teamff4fecb2018-12-19 11:31:37 +0800125
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800126 a.recycle();
127
Setup Wizard Team1ed30732019-03-14 10:00:10 +0800128 if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) {
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800129 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
130 }
131
Setup Wizard Teamff4fecb2018-12-19 11:31:37 +0800132 registerMixin(
Setup Wizard Team67e7fef2019-03-23 08:06:48 +0800133 StatusBarMixin.class, new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr));
134 registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, activity.getWindow()));
135 registerMixin(FooterBarMixin.class, new FooterBarMixin(this, attrs, defStyleAttr));
Setup Wizard Teamff4fecb2018-12-19 11:31:37 +0800136
Setup Wizard Team1ed30732019-03-14 10:00:10 +0800137 getMixin(SystemNavBarMixin.class).applyPartnerCustomizations(attrs, defStyleAttr);
138
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800139 // Override the FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_TRANSLUCENT_STATUS,
140 // FLAG_TRANSLUCENT_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attributes of window forces
141 // showing status bar and navigation bar.
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800142 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
143 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
144 activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
145 activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
146 }
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800147 }
148
149 @Override
150 protected View onInflateTemplate(LayoutInflater inflater, int template) {
151 if (template == 0) {
152 template = R.layout.partner_customization_layout;
153 }
154 return inflateTemplate(inflater, 0, template);
155 }
156
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800157 /**
158 * {@inheritDoc}
159 *
160 * <p>This method sets all these flags before onTemplateInflated since it will be too late and get
161 * incorrect flag value on PartnerCustomizationLayout if sets them after onTemplateInflated.
162 */
163 @Override
164 protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {
165
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800166 // Sets default value to true since this timing
167 // before PartnerCustomization members initialization
168 usePartnerResourceAttr = true;
169
170 activity = lookupActivityFromContext(getContext());
171
Setup Wizard Team75de5902020-09-22 20:34:53 +0800172 boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800173
174 TypedArray a =
175 getContext()
176 .obtainStyledAttributes(
177 attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
178
179 if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) {
Setup Wizard Team5b6b1b32019-04-02 13:42:22 +0800180 // TODO: Enable Log.WTF after other client already set sucUsePartnerResource.
Setup Wizard Teamf513dd22021-06-10 15:49:36 +0800181 LOG.e("Attribute sucUsePartnerResource not found in " + activity.getComponentName());
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800182 }
183
184 usePartnerResourceAttr =
185 isSetupFlow
186 || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true);
187
Setup Wizard Team8a37aa82021-04-26 10:01:19 +0800188 useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor);
189 useFullDynamicColorAttr =
190 a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false);
191
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800192 a.recycle();
193
Setup Wizard Teamf513dd22021-06-10 15:49:36 +0800194 LOG.atDebug(
195 "activity="
196 + activity.getClass().getSimpleName()
197 + " isSetupFlow="
198 + isSetupFlow
199 + " enablePartnerResourceLoading="
200 + enablePartnerResourceLoading()
201 + " usePartnerResourceAttr="
202 + usePartnerResourceAttr
203 + " useDynamicColor="
204 + useDynamicColor
205 + " useFullDynamicColorAttr="
206 + useFullDynamicColorAttr);
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800207 }
208
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800209 @Override
210 protected ViewGroup findContainer(int containerId) {
211 if (containerId == 0) {
212 containerId = R.id.suc_layout_content;
213 }
214 return super.findContainer(containerId);
215 }
216
217 @Override
218 protected void onAttachedToWindow() {
219 super.onAttachedToWindow();
Setup Wizard Team1d79d002018-12-13 14:30:13 +0800220 LifecycleFragment.attachNow(activity);
Setup Wizard Teamaa4a4b72023-10-16 04:39:29 +0000221 if (WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
Setup Wizard Team55addcd2022-01-06 23:50:39 +0800222 getViewTreeObserver().addOnWindowFocusChangeListener(windowFocusChangeListener);
223 }
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +0800224 getMixin(FooterBarMixin.class).onAttachedToWindow();
Setup Wizard Team1d79d002018-12-13 14:30:13 +0800225 }
226
227 @Override
228 protected void onDetachedFromWindow() {
229 super.onDetachedFromWindow();
Setup Wizard Team55addcd2022-01-06 23:50:39 +0800230 if (VERSION.SDK_INT >= Build.VERSION_CODES.Q
Setup Wizard Teamde9b52a2019-04-01 10:27:47 +0800231 && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +0800232 FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class);
233 footerBarMixin.onDetachedFromWindow();
234 FooterButton primaryButton = footerBarMixin.getPrimaryButton();
235 FooterButton secondaryButton = footerBarMixin.getSecondaryButton();
236 PersistableBundle primaryButtonMetrics =
237 primaryButton != null
238 ? primaryButton.getMetrics("PrimaryFooterButton")
239 : PersistableBundle.EMPTY;
240 PersistableBundle secondaryButtonMetrics =
241 secondaryButton != null
242 ? secondaryButton.getMetrics("SecondaryFooterButton")
243 : PersistableBundle.EMPTY;
244
David Liu534db6a2023-02-15 06:55:44 +0000245 PersistableBundle layoutTypeMetrics =
246 (layoutTypeBundle != null) ? layoutTypeBundle : PersistableBundle.EMPTY;
247
Setup Wizard Teamd6fc4af2019-12-31 20:58:11 +0800248 PersistableBundle persistableBundle =
249 PersistableBundles.mergeBundles(
David Liu534db6a2023-02-15 06:55:44 +0000250 footerBarMixin.getLoggingMetrics(),
251 primaryButtonMetrics,
252 secondaryButtonMetrics,
253 layoutTypeMetrics);
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +0800254
Setup Wizard Team1d79d002018-12-13 14:30:13 +0800255 SetupMetricsLogger.logCustomEvent(
256 getContext(),
Setup Wizard Team24bb1e22019-11-08 15:22:51 +0800257 CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle));
Setup Wizard Team1d79d002018-12-13 14:30:13 +0800258 }
Setup Wizard Teamaa4a4b72023-10-16 04:39:29 +0000259 getViewTreeObserver().removeOnWindowFocusChangeListener(windowFocusChangeListener);
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800260 }
261
David Liu534db6a2023-02-15 06:55:44 +0000262 /**
Setup Wizard Team4f5bbf42023-12-20 03:05:46 +0000263 * PartnerCustomizationLayout is a template layout for different type of GlifLayout. This method
264 * allows each type of layout to report its "GlifLayoutType".
David Liu534db6a2023-02-15 06:55:44 +0000265 */
266 public void setLayoutTypeMetrics(PersistableBundle bundle) {
267 this.layoutTypeBundle = bundle;
268 }
269
270 /** Returns a {@link PersistableBundle} contains key "GlifLayoutType". */
271 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
272 public PersistableBundle getLayoutTypeMetrics() {
273 return this.layoutTypeBundle;
274 }
275
Setup Wizard Team642e0962020-12-30 19:23:06 +0800276 public static Activity lookupActivityFromContext(Context context) {
Setup Wizard Teamaa4a4b72023-10-16 04:39:29 +0000277 return PartnerConfigHelper.lookupActivityFromContext(context);
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800278 }
279
280 /**
Setup Wizard Team67e7fef2019-03-23 08:06:48 +0800281 * Returns true if partner resource loading is enabled. If true, and other necessary conditions
282 * for loading theme attributes are met, this layout will use customized theme attributes from OEM
283 * overlays. This is intended to be used with flag-based development, to allow a flag to control
284 * the rollout of partner resource loading.
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +0800285 */
Setup Wizard Team67e7fef2019-03-23 08:06:48 +0800286 protected boolean enablePartnerResourceLoading() {
Setup Wizard Teamd41e3be2019-01-17 16:07:58 +0800287 return true;
288 }
289
Setup Wizard Team1ed30732019-03-14 10:00:10 +0800290 /** Returns if the current layout/activity applies partner customized configurations or not. */
Setup Wizard Team67e7fef2019-03-23 08:06:48 +0800291 public boolean shouldApplyPartnerResource() {
292 if (!enablePartnerResourceLoading()) {
293 return false;
294 }
295 if (!usePartnerResourceAttr) {
296 return false;
297 }
Setup Wizard Team3266cd82019-05-09 19:31:02 +0800298 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Setup Wizard Team67e7fef2019-03-23 08:06:48 +0800299 return false;
300 }
301 if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
302 return false;
303 }
304 return true;
Setup Wizard Team1ed30732019-03-14 10:00:10 +0800305 }
Setup Wizard Team8a37aa82021-04-26 10:01:19 +0800306
307 /**
308 * Returns {@code true} if the current layout/activity applies dynamic color. Otherwise, returns
309 * {@code false}.
310 */
311 public boolean shouldApplyDynamicColor() {
312 if (!useDynamicColor) {
313 return false;
314 }
315 if (!BuildCompatUtils.isAtLeastS()) {
316 return false;
317 }
318 if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
319 return false;
320 }
321 return true;
322 }
323
324 /**
325 * Returns {@code true} if the current layout/activity applies full dynamic color. Otherwise,
326 * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()}
327 * and the value of the {@code app:sucFullDynamicColor}.
328 */
329 public boolean useFullDynamicColor() {
330 return shouldApplyDynamicColor() && useFullDynamicColorAttr;
331 }
Setup Wizard Team55addcd2022-01-06 23:50:39 +0800332
333 /**
Setup Wizard Team4f5bbf42023-12-20 03:05:46 +0000334 * Sets a logging observer for {@link FooterBarMixin}. The logging observer is used to log
335 * impressions and clicks on the layout and footer bar buttons.
336 *
337 * @throws UnsupportedOperationException if the primary or secondary button has been set before
338 * the logging observer is set
339 */
340 public void setLoggingObserver(LoggingObserver loggingObserver) {
341 getMixin(FooterBarMixin.class).setLoggingObserver(loggingObserver);
342 loggingObserver.log(new LayoutInflatedEvent(this));
343 }
344
345 /**
Setup Wizard Team55addcd2022-01-06 23:50:39 +0800346 * Invoke the method onFocusStatusChanged when onWindowFocusChangeListener receive onFocusChanged.
347 */
348 private void onFocusChanged(boolean hasFocus) {
349 SetupCompatServiceInvoker.get(getContext())
350 .onFocusStatusChanged(
351 FocusChangedMetricHelper.getScreenName(activity),
352 FocusChangedMetricHelper.getExtraBundle(
353 activity, PartnerCustomizationLayout.this, hasFocus));
354 }
Setup Wizard Team8ccc9e62018-11-28 13:33:48 +0800355}