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