| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 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 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 19 | import android.app.Activity; |
| 20 | import android.content.Context; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 21 | import android.content.res.TypedArray; |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 22 | import android.os.Build; |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 23 | import android.os.Build.VERSION; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 24 | import android.os.Build.VERSION_CODES; |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 25 | import android.os.PersistableBundle; |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 26 | import androidx.fragment.app.Fragment; |
| 27 | import androidx.fragment.app.FragmentActivity; |
| 28 | import androidx.fragment.app.FragmentManager; |
| 29 | import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 30 | import android.util.AttributeSet; |
| 31 | import android.view.LayoutInflater; |
| 32 | import android.view.View; |
| 33 | import android.view.ViewGroup; |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 34 | import android.view.ViewTreeObserver; |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 35 | import android.view.WindowInsets; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 36 | import android.view.WindowManager; |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 37 | import android.widget.LinearLayout; |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 38 | import androidx.annotation.VisibleForTesting; |
| 39 | import com.google.android.setupcompat.internal.FocusChangedMetricHelper; |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 40 | import com.google.android.setupcompat.internal.LifecycleFragment; |
| Setup Wizard Team | d6fc4af | 2019-12-31 20:58:11 +0800 | [diff] [blame] | 41 | import com.google.android.setupcompat.internal.PersistableBundles; |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 42 | import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; |
| Maurice Lam | c452932 | 2019-02-14 22:04:56 +0000 | [diff] [blame] | 43 | import com.google.android.setupcompat.internal.TemplateLayout; |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 44 | import com.google.android.setupcompat.logging.CustomEvent; |
| Setup Wizard Team | 4f5bbf4 | 2023-12-20 03:05:46 +0000 | [diff] [blame] | 45 | import com.google.android.setupcompat.logging.LoggingObserver; |
| 46 | import com.google.android.setupcompat.logging.LoggingObserver.SetupCompatUiEvent.LayoutInflatedEvent; |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 47 | import com.google.android.setupcompat.logging.MetricKey; |
| 48 | import com.google.android.setupcompat.logging.SetupMetricsLogger; |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 49 | import com.google.android.setupcompat.partnerconfig.PartnerConfig; |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 50 | import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 51 | import com.google.android.setupcompat.template.FooterBarMixin; |
| 52 | import com.google.android.setupcompat.template.FooterButton; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 53 | import com.google.android.setupcompat.template.StatusBarMixin; |
| 54 | import com.google.android.setupcompat.template.SystemNavBarMixin; |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 55 | import com.google.android.setupcompat.util.BuildCompatUtils; |
| Setup Wizard Team | f513dd2 | 2021-06-10 15:49:36 +0800 | [diff] [blame] | 56 | import com.google.android.setupcompat.util.Logger; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 57 | import com.google.android.setupcompat.util.WizardManagerHelper; |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 58 | import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 59 | import org.jspecify.annotations.NonNull; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 60 | |
| 61 | /** A templatization layout with consistent style used in Setup Wizard or app itself. */ |
| 62 | public class PartnerCustomizationLayout extends TemplateLayout { |
| Setup Wizard Team | f513dd2 | 2021-06-10 15:49:36 +0800 | [diff] [blame] | 63 | |
| 64 | private static final Logger LOG = new Logger("PartnerCustomizationLayout"); |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 65 | |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 66 | /** |
| 67 | * Attribute indicating whether usage of partner theme resources is allowed. This corresponds to |
| 68 | * the {@code app:sucUsePartnerResource} XML attribute. Note that when running in setup wizard, |
| 69 | * this is always overridden to true. |
| 70 | */ |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 71 | private boolean usePartnerResourceAttr; |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 72 | |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 73 | /** |
| 74 | * Attribute indicating whether using full dynamic colors or not. This corresponds to the {@code |
| 75 | * app:sucFullDynamicColor} XML attribute. |
| 76 | */ |
| 77 | private boolean useFullDynamicColorAttr; |
| 78 | |
| 79 | /** |
| 80 | * Attribute indicating whether usage of dynamic is allowed. This corresponds to the existence of |
| 81 | * {@code app:sucFullDynamicColor} XML attribute. |
| 82 | */ |
| 83 | private boolean useDynamicColor; |
| 84 | |
| Pasty Chang | c2366eb | 2025-01-14 18:11:56 -0800 | [diff] [blame] | 85 | protected Activity activity; |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 86 | |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 87 | private PersistableBundle layoutTypeBundle; |
| 88 | |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 89 | @VisibleForTesting FragmentLifecycleCallbacks fragmentLifecycleCallbacks; |
| 90 | |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 91 | private int footerBarPaddingBottom; |
| 92 | |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 93 | @CanIgnoreReturnValue |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 94 | public PartnerCustomizationLayout(Context context) { |
| 95 | this(context, 0, 0); |
| 96 | } |
| 97 | |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 98 | @CanIgnoreReturnValue |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 99 | public PartnerCustomizationLayout(Context context, int template) { |
| 100 | this(context, template, 0); |
| 101 | } |
| 102 | |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 103 | @CanIgnoreReturnValue |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 104 | public PartnerCustomizationLayout(Context context, int template, int containerId) { |
| 105 | super(context, template, containerId); |
| 106 | init(null, R.attr.sucLayoutTheme); |
| 107 | } |
| 108 | |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 109 | @CanIgnoreReturnValue |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 110 | public PartnerCustomizationLayout(Context context, AttributeSet attrs) { |
| 111 | super(context, attrs); |
| 112 | init(attrs, R.attr.sucLayoutTheme); |
| 113 | } |
| 114 | |
| Setup Wizard Team | 0d3126a | 2022-04-27 09:03:55 +0800 | [diff] [blame] | 115 | @CanIgnoreReturnValue |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 116 | public PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr) { |
| 117 | super(context, attrs, defStyleAttr); |
| 118 | init(attrs, defStyleAttr); |
| 119 | } |
| 120 | |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 121 | @VisibleForTesting |
| 122 | final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener = |
| 123 | this::onFocusChanged; |
| 124 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 125 | private void init(AttributeSet attrs, int defStyleAttr) { |
| Setup Wizard Team | 261f8b4 | 2021-03-04 09:18:37 +0800 | [diff] [blame] | 126 | if (isInEditMode()) { |
| 127 | return; |
| 128 | } |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 129 | |
| 130 | TypedArray a = |
| 131 | getContext() |
| 132 | .obtainStyledAttributes( |
| 133 | attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0); |
| 134 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 135 | boolean layoutFullscreen = |
| 136 | a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucLayoutFullscreen, true); |
| Setup Wizard Team | ff4fecb | 2018-12-19 11:31:37 +0800 | [diff] [blame] | 137 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 138 | a.recycle(); |
| 139 | |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 140 | // Get the footer bar default padding bottom value. |
| 141 | TypedArray footerBarMixinAttrs = |
| 142 | getContext().obtainStyledAttributes(attrs, R.styleable.SucFooterBarMixin, defStyleAttr, 0); |
| 143 | int defaultPadding = |
| 144 | footerBarMixinAttrs.getDimensionPixelSize( |
| 145 | R.styleable.SucFooterBarMixin_sucFooterBarPaddingVertical, 0); |
| 146 | footerBarPaddingBottom = |
| 147 | footerBarMixinAttrs.getDimensionPixelSize( |
| 148 | R.styleable.SucFooterBarMixin_sucFooterBarPaddingBottom, defaultPadding); |
| 149 | |
| 150 | footerBarMixinAttrs.recycle(); |
| 151 | |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 152 | if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) { |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 153 | setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); |
| 154 | } |
| 155 | |
| Setup Wizard Team | ff4fecb | 2018-12-19 11:31:37 +0800 | [diff] [blame] | 156 | registerMixin( |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 157 | StatusBarMixin.class, new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr)); |
| 158 | registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, activity.getWindow())); |
| 159 | registerMixin(FooterBarMixin.class, new FooterBarMixin(this, attrs, defStyleAttr)); |
| Setup Wizard Team | ff4fecb | 2018-12-19 11:31:37 +0800 | [diff] [blame] | 160 | |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 161 | getMixin(SystemNavBarMixin.class).applyPartnerCustomizations(attrs, defStyleAttr); |
| 162 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 163 | // Override the FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_TRANSLUCENT_STATUS, |
| 164 | // FLAG_TRANSLUCENT_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attributes of window forces |
| 165 | // showing status bar and navigation bar. |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 166 | if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { |
| 167 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); |
| 168 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); |
| 169 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); |
| 170 | } |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | @Override |
| 174 | protected View onInflateTemplate(LayoutInflater inflater, int template) { |
| 175 | if (template == 0) { |
| 176 | template = R.layout.partner_customization_layout; |
| 177 | } |
| 178 | return inflateTemplate(inflater, 0, template); |
| 179 | } |
| 180 | |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 181 | /** |
| 182 | * {@inheritDoc} |
| 183 | * |
| 184 | * <p>This method sets all these flags before onTemplateInflated since it will be too late and get |
| 185 | * incorrect flag value on PartnerCustomizationLayout if sets them after onTemplateInflated. |
| 186 | */ |
| 187 | @Override |
| 188 | protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) { |
| 189 | |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 190 | // Sets default value to true since this timing |
| 191 | // before PartnerCustomization members initialization |
| 192 | usePartnerResourceAttr = true; |
| 193 | |
| 194 | activity = lookupActivityFromContext(getContext()); |
| 195 | |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 196 | LOG.atDebug( |
| 197 | "Flag of isEnhancedSetupDesignMetricsEnabled=" |
| 198 | + PartnerConfigHelper.isEnhancedSetupDesignMetricsEnabled(getContext())); |
| 199 | if (PartnerConfigHelper.isEnhancedSetupDesignMetricsEnabled(getContext())) { |
| 200 | tryRegisterFragmentCallbacks(activity); |
| 201 | } |
| 202 | |
| Setup Wizard Team | 75de590 | 2020-09-22 20:34:53 +0800 | [diff] [blame] | 203 | boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent()); |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 204 | |
| 205 | TypedArray a = |
| 206 | getContext() |
| 207 | .obtainStyledAttributes( |
| 208 | attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0); |
| 209 | |
| 210 | if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) { |
| Setup Wizard Team | 5b6b1b3 | 2019-04-02 13:42:22 +0800 | [diff] [blame] | 211 | // TODO: Enable Log.WTF after other client already set sucUsePartnerResource. |
| Setup Wizard Team | f513dd2 | 2021-06-10 15:49:36 +0800 | [diff] [blame] | 212 | LOG.e("Attribute sucUsePartnerResource not found in " + activity.getComponentName()); |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | usePartnerResourceAttr = |
| 216 | isSetupFlow |
| 217 | || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true); |
| 218 | |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 219 | useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor); |
| 220 | useFullDynamicColorAttr = |
| 221 | a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false); |
| 222 | |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 223 | a.recycle(); |
| 224 | |
| Setup Wizard Team | f513dd2 | 2021-06-10 15:49:36 +0800 | [diff] [blame] | 225 | LOG.atDebug( |
| 226 | "activity=" |
| 227 | + activity.getClass().getSimpleName() |
| 228 | + " isSetupFlow=" |
| 229 | + isSetupFlow |
| 230 | + " enablePartnerResourceLoading=" |
| 231 | + enablePartnerResourceLoading() |
| 232 | + " usePartnerResourceAttr=" |
| 233 | + usePartnerResourceAttr |
| 234 | + " useDynamicColor=" |
| 235 | + useDynamicColor |
| 236 | + " useFullDynamicColorAttr=" |
| 237 | + useFullDynamicColorAttr); |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 238 | } |
| 239 | |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 240 | private void printFragmentInfoAtDebug(Fragment fragment, String tag) { |
| 241 | if (fragment == null) { |
| 242 | return; |
| 243 | } |
| 244 | int fragmentId = fragment.getId(); |
| 245 | String fragmentName = tryGetResourceEntryName(fragmentId); |
| 246 | LOG.atDebug( |
| 247 | tag |
| 248 | + " fragment name=" |
| 249 | + fragment.getClass().getSimpleName() |
| 250 | + ", tag=" |
| 251 | + fragment.getTag() |
| 252 | + ", id=" |
| 253 | + fragment.getId() |
| 254 | + ", name=" |
| 255 | + fragmentName); |
| 256 | } |
| 257 | |
| 258 | private String tryGetResourceEntryName(int fragmentId) { |
| 259 | return (fragmentId == 0) ? "" : getResources().getResourceEntryName(fragmentId); |
| 260 | } |
| 261 | |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 262 | @Override |
| 263 | protected ViewGroup findContainer(int containerId) { |
| 264 | if (containerId == 0) { |
| 265 | containerId = R.id.suc_layout_content; |
| 266 | } |
| 267 | return super.findContainer(containerId); |
| 268 | } |
| 269 | |
| 270 | @Override |
| 271 | protected void onAttachedToWindow() { |
| 272 | super.onAttachedToWindow(); |
| Pasty Chang | cec90d0 | 2025-02-19 17:38:43 -0800 | [diff] [blame] | 273 | LifecycleFragment lifecycleFragment = |
| 274 | LifecycleFragment.attachNow(activity, this::logFooterButtonMetrics); |
| Pasty Chang | 1ca08d8 | 2025-02-16 18:10:28 -0800 | [diff] [blame] | 275 | if (lifecycleFragment == null) { |
| 276 | LOG.atDebug( |
| 277 | "Unable to attach lifecycle fragment to the host activity. Activity=" |
| 278 | + ((activity != null) ? activity.getClass().getSimpleName() : "null")); |
| 279 | } |
| 280 | |
| Setup Wizard Team | aa4a4b7 | 2023-10-16 04:39:29 +0000 | [diff] [blame] | 281 | if (WizardManagerHelper.isAnySetupWizard(activity.getIntent())) { |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 282 | getViewTreeObserver().addOnWindowFocusChangeListener(windowFocusChangeListener); |
| 283 | } |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 284 | getMixin(FooterBarMixin.class).onAttachedToWindow(); |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 285 | } |
| 286 | |
| 287 | @Override |
| 288 | protected void onDetachedFromWindow() { |
| 289 | super.onDetachedFromWindow(); |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 290 | if (VERSION.SDK_INT >= Build.VERSION_CODES.Q |
| Setup Wizard Team | de9b52a | 2019-04-01 10:27:47 +0800 | [diff] [blame] | 291 | && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) { |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 292 | FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); |
| 293 | footerBarMixin.onDetachedFromWindow(); |
| 294 | FooterButton primaryButton = footerBarMixin.getPrimaryButton(); |
| 295 | FooterButton secondaryButton = footerBarMixin.getSecondaryButton(); |
| 296 | PersistableBundle primaryButtonMetrics = |
| 297 | primaryButton != null |
| 298 | ? primaryButton.getMetrics("PrimaryFooterButton") |
| 299 | : PersistableBundle.EMPTY; |
| 300 | PersistableBundle secondaryButtonMetrics = |
| 301 | secondaryButton != null |
| 302 | ? secondaryButton.getMetrics("SecondaryFooterButton") |
| 303 | : PersistableBundle.EMPTY; |
| 304 | |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 305 | PersistableBundle layoutTypeMetrics = |
| 306 | (layoutTypeBundle != null) ? layoutTypeBundle : PersistableBundle.EMPTY; |
| 307 | |
| Setup Wizard Team | d6fc4af | 2019-12-31 20:58:11 +0800 | [diff] [blame] | 308 | PersistableBundle persistableBundle = |
| 309 | PersistableBundles.mergeBundles( |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 310 | footerBarMixin.getLoggingMetrics(), |
| 311 | primaryButtonMetrics, |
| 312 | secondaryButtonMetrics, |
| 313 | layoutTypeMetrics); |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 314 | |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 315 | SetupMetricsLogger.logCustomEvent( |
| 316 | getContext(), |
| Setup Wizard Team | 24bb1e2 | 2019-11-08 15:22:51 +0800 | [diff] [blame] | 317 | CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle)); |
| Setup Wizard Team | 1d79d00 | 2018-12-13 14:30:13 +0800 | [diff] [blame] | 318 | } |
| Setup Wizard Team | aa4a4b7 | 2023-10-16 04:39:29 +0000 | [diff] [blame] | 319 | getViewTreeObserver().removeOnWindowFocusChangeListener(windowFocusChangeListener); |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 320 | |
| 321 | if (PartnerConfigHelper.isEnhancedSetupDesignMetricsEnabled(getContext())) { |
| 322 | tryUnregisterFragmentCallbacks(activity); |
| 323 | } |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 324 | } |
| 325 | |
| Pasty Chang | 1912cc6 | 2025-03-18 22:37:26 -0700 | [diff] [blame^] | 326 | private void logFooterButtonMetrics(PersistableBundle bundle) { |
| Pasty Chang | cec90d0 | 2025-02-19 17:38:43 -0800 | [diff] [blame] | 327 | if (VERSION.SDK_INT >= Build.VERSION_CODES.Q |
| 328 | && activity != null |
| 329 | && WizardManagerHelper.isAnySetupWizard(activity.getIntent()) |
| 330 | && PartnerConfigHelper.isEnhancedSetupDesignMetricsEnabled(getContext())) { |
| 331 | FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); |
| 332 | |
| 333 | if (footerBarMixin == null |
| 334 | || (footerBarMixin.getPrimaryButton() == null |
| 335 | && footerBarMixin.getSecondaryButton() == null)) { |
| 336 | LOG.atDebug("Skip footer button logging because no footer buttons."); |
| 337 | return; |
| 338 | } |
| 339 | |
| 340 | footerBarMixin.onDetachedFromWindow(); |
| 341 | FooterButton primaryButton = footerBarMixin.getPrimaryButton(); |
| 342 | FooterButton secondaryButton = footerBarMixin.getSecondaryButton(); |
| 343 | PersistableBundle primaryButtonMetrics = |
| 344 | primaryButton != null |
| 345 | ? primaryButton.getMetrics("PrimaryFooterButton") |
| 346 | : PersistableBundle.EMPTY; |
| 347 | PersistableBundle secondaryButtonMetrics = |
| 348 | secondaryButton != null |
| 349 | ? secondaryButton.getMetrics("SecondaryFooterButton") |
| 350 | : PersistableBundle.EMPTY; |
| 351 | |
| 352 | PersistableBundle persistableBundle = |
| 353 | PersistableBundles.mergeBundles( |
| Pasty Chang | 1912cc6 | 2025-03-18 22:37:26 -0700 | [diff] [blame^] | 354 | footerBarMixin.getLoggingMetrics(), |
| 355 | primaryButtonMetrics, |
| 356 | secondaryButtonMetrics, |
| 357 | bundle); |
| Pasty Chang | cec90d0 | 2025-02-19 17:38:43 -0800 | [diff] [blame] | 358 | |
| 359 | SetupMetricsLogger.logCustomEvent( |
| 360 | getContext(), |
| 361 | CustomEvent.create(MetricKey.get("FooterButtonMetrics", activity), persistableBundle)); |
| 362 | } |
| 363 | } |
| 364 | |
| Pasty Chang | f425dc3 | 2025-03-02 22:16:51 -0800 | [diff] [blame] | 365 | private void tryRegisterFragmentCallbacks(Activity activity) { |
| 366 | if ((activity instanceof FragmentActivity fragmentActivity)) { |
| 367 | fragmentLifecycleCallbacks = |
| 368 | new FragmentLifecycleCallbacks() { |
| 369 | @Override |
| 370 | public void onFragmentAttached( |
| 371 | @NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) { |
| 372 | printFragmentInfoAtDebug(f, "onFragmentAttached"); |
| 373 | getMixin(FooterBarMixin.class).setFragmentInfo(f); |
| 374 | super.onFragmentAttached(fm, f, context); |
| 375 | } |
| 376 | }; |
| 377 | |
| 378 | fragmentActivity |
| 379 | .getSupportFragmentManager() |
| 380 | .registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true); |
| 381 | LOG.atDebug( |
| 382 | "Register the onFragmentAttached lifecycle callbacks to " |
| 383 | + activity.getClass().getSimpleName()); |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | private void tryUnregisterFragmentCallbacks(Activity activity) { |
| 388 | if ((activity instanceof FragmentActivity fragmentActivity)) { |
| 389 | fragmentActivity |
| 390 | .getSupportFragmentManager() |
| 391 | .unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks); |
| 392 | } |
| 393 | } |
| 394 | |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 395 | /** |
| Setup Wizard Team | 4f5bbf4 | 2023-12-20 03:05:46 +0000 | [diff] [blame] | 396 | * PartnerCustomizationLayout is a template layout for different type of GlifLayout. This method |
| 397 | * allows each type of layout to report its "GlifLayoutType". |
| David Liu | 534db6a | 2023-02-15 06:55:44 +0000 | [diff] [blame] | 398 | */ |
| 399 | public void setLayoutTypeMetrics(PersistableBundle bundle) { |
| 400 | this.layoutTypeBundle = bundle; |
| 401 | } |
| 402 | |
| 403 | /** Returns a {@link PersistableBundle} contains key "GlifLayoutType". */ |
| 404 | @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) |
| 405 | public PersistableBundle getLayoutTypeMetrics() { |
| 406 | return this.layoutTypeBundle; |
| 407 | } |
| 408 | |
| Setup Wizard Team | 642e096 | 2020-12-30 19:23:06 +0800 | [diff] [blame] | 409 | public static Activity lookupActivityFromContext(Context context) { |
| Setup Wizard Team | aa4a4b7 | 2023-10-16 04:39:29 +0000 | [diff] [blame] | 410 | return PartnerConfigHelper.lookupActivityFromContext(context); |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 411 | } |
| 412 | |
| 413 | /** |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 414 | * Returns true if partner resource loading is enabled. If true, and other necessary conditions |
| 415 | * for loading theme attributes are met, this layout will use customized theme attributes from OEM |
| 416 | * overlays. This is intended to be used with flag-based development, to allow a flag to control |
| 417 | * the rollout of partner resource loading. |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 418 | */ |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 419 | protected boolean enablePartnerResourceLoading() { |
| Setup Wizard Team | d41e3be | 2019-01-17 16:07:58 +0800 | [diff] [blame] | 420 | return true; |
| 421 | } |
| 422 | |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 423 | /** Returns if the current layout/activity applies partner customized configurations or not. */ |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 424 | public boolean shouldApplyPartnerResource() { |
| 425 | if (!enablePartnerResourceLoading()) { |
| 426 | return false; |
| 427 | } |
| 428 | if (!usePartnerResourceAttr) { |
| 429 | return false; |
| 430 | } |
| Setup Wizard Team | 3266cd8 | 2019-05-09 19:31:02 +0800 | [diff] [blame] | 431 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { |
| Setup Wizard Team | 67e7fef | 2019-03-23 08:06:48 +0800 | [diff] [blame] | 432 | return false; |
| 433 | } |
| 434 | if (!PartnerConfigHelper.get(getContext()).isAvailable()) { |
| 435 | return false; |
| 436 | } |
| 437 | return true; |
| Setup Wizard Team | 1ed3073 | 2019-03-14 10:00:10 +0800 | [diff] [blame] | 438 | } |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 439 | |
| 440 | /** |
| 441 | * Returns {@code true} if the current layout/activity applies dynamic color. Otherwise, returns |
| 442 | * {@code false}. |
| 443 | */ |
| 444 | public boolean shouldApplyDynamicColor() { |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 445 | if (!BuildCompatUtils.isAtLeastS()) { |
| 446 | return false; |
| 447 | } |
| Pasty Chang | 0a43918 | 2024-11-18 03:02:38 +0000 | [diff] [blame] | 448 | |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 449 | if (!PartnerConfigHelper.get(getContext()).isAvailable()) { |
| 450 | return false; |
| 451 | } |
| Pasty Chang | 0a43918 | 2024-11-18 03:02:38 +0000 | [diff] [blame] | 452 | |
| 453 | // If the dynamic theme is applied, useDynamicColor would be true and shouldApplyDynamicColor |
| 454 | // would return true. |
| 455 | if (useDynamicColor) { |
| 456 | return true; |
| 457 | } |
| 458 | if (!PartnerConfigHelper.isSetupWizardDynamicColorEnabled(getContext())) { |
| 459 | return false; |
| 460 | } |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 461 | return true; |
| 462 | } |
| 463 | |
| 464 | /** |
| 465 | * Returns {@code true} if the current layout/activity applies full dynamic color. Otherwise, |
| Pasty Chang | fc90bfe | 2024-10-07 12:01:17 +0000 | [diff] [blame] | 466 | * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()}, |
| 467 | * the value of the {@code app:sucFullDynamicColor}, and the result of {@link |
| 468 | * PartnerConfigHelper#isSetupWizardFullDynamicColorEnabled(Context)}. |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 469 | */ |
| 470 | public boolean useFullDynamicColor() { |
| Pasty Chang | fc90bfe | 2024-10-07 12:01:17 +0000 | [diff] [blame] | 471 | return shouldApplyDynamicColor() |
| 472 | && (useFullDynamicColorAttr |
| 473 | || PartnerConfigHelper.isSetupWizardFullDynamicColorEnabled(getContext())); |
| Setup Wizard Team | 8a37aa8 | 2021-04-26 10:01:19 +0800 | [diff] [blame] | 474 | } |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 475 | |
| 476 | /** |
| Pasty Chang | fc90bfe | 2024-10-07 12:01:17 +0000 | [diff] [blame] | 477 | * Sets a logging observer for {@link FooterBarMixin}. The logging observer is used to log UI |
| 478 | * events (e.g. page impressions and button clicks) on the layout and footer bar buttons. |
| Setup Wizard Team | 4f5bbf4 | 2023-12-20 03:05:46 +0000 | [diff] [blame] | 479 | */ |
| 480 | public void setLoggingObserver(LoggingObserver loggingObserver) { |
| Setup Wizard Team | 4f5bbf4 | 2023-12-20 03:05:46 +0000 | [diff] [blame] | 481 | loggingObserver.log(new LayoutInflatedEvent(this)); |
| Pasty Chang | fc90bfe | 2024-10-07 12:01:17 +0000 | [diff] [blame] | 482 | getMixin(FooterBarMixin.class).setLoggingObserver(loggingObserver); |
| Setup Wizard Team | 4f5bbf4 | 2023-12-20 03:05:46 +0000 | [diff] [blame] | 483 | } |
| 484 | |
| 485 | /** |
| Setup Wizard Team | 55addcd | 2022-01-06 23:50:39 +0800 | [diff] [blame] | 486 | * Invoke the method onFocusStatusChanged when onWindowFocusChangeListener receive onFocusChanged. |
| 487 | */ |
| 488 | private void onFocusChanged(boolean hasFocus) { |
| 489 | SetupCompatServiceInvoker.get(getContext()) |
| 490 | .onFocusStatusChanged( |
| 491 | FocusChangedMetricHelper.getScreenName(activity), |
| 492 | FocusChangedMetricHelper.getExtraBundle( |
| 493 | activity, PartnerCustomizationLayout.this, hasFocus)); |
| 494 | } |
| Pasty Chang | f3ba27a | 2025-02-23 23:52:07 -0800 | [diff] [blame] | 495 | |
| 496 | @Override |
| 497 | public WindowInsets onApplyWindowInsets(WindowInsets insets) { |
| 498 | // TODO: b/398407478 - Add test case for edge to edge to layout from library. |
| 499 | if (PartnerConfigHelper.isGlifExpressiveEnabled(getContext())) { |
| 500 | // Edge to edge extend the footer bar padding bottom to the navigation bar height. |
| 501 | if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && insets.getSystemWindowInsetBottom() > 0) { |
| 502 | LOG.atDebug("NavigationBarHeight: " + insets.getSystemWindowInsetBottom()); |
| 503 | FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); |
| 504 | LinearLayout buttonContainer = footerBarMixin.getButtonContainer(); |
| 505 | if (footerBarMixin != null && footerBarMixin.getButtonContainer() != null) { |
| 506 | if (PartnerConfigHelper.get(getContext()) |
| 507 | .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM)) { |
| 508 | footerBarPaddingBottom = |
| 509 | (int) |
| 510 | PartnerConfigHelper.get(getContext()) |
| 511 | .getDimension( |
| 512 | getContext(), PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); |
| 513 | } |
| 514 | // Adjust footer bar padding to account for the navigation bar, ensuring |
| 515 | // it extends to the bottom of the screen and with proper bottom padding. |
| 516 | buttonContainer.setPadding( |
| 517 | buttonContainer.getPaddingLeft(), |
| 518 | buttonContainer.getPaddingTop(), |
| 519 | buttonContainer.getPaddingRight(), |
| 520 | footerBarPaddingBottom + insets.getSystemWindowInsetBottom()); |
| 521 | } |
| 522 | } |
| 523 | } |
| 524 | return super.onApplyWindowInsets(insets); |
| 525 | } |
| Setup Wizard Team | 8ccc9e6 | 2018-11-28 13:33:48 +0800 | [diff] [blame] | 526 | } |