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