diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-05-11 23:17:42 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-05-11 23:17:42 +0000 |
commit | 4148989c031617b317b128b71ef1c49dd21b5e92 (patch) | |
tree | b71b724c0225aac13934f68cafc39737011ef49a | |
parent | 23051c853af2ff83efa8d0f1587757f83897e6de (diff) | |
parent | 3266cd8b60eab2ff75573e7df5ff62b2a8f7db00 (diff) | |
download | setupcompat-4148989c031617b317b128b71ef1c49dd21b5e92.tar.gz |
Snap for 5558509 from 3266cd8b60eab2ff75573e7df5ff62b2a8f7db00 to qt-release
Change-Id: Ie408a01fe0b90210ed542e92ba57452970d4a37c
15 files changed, 662 insertions, 278 deletions
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 95d6af4..360a0a0 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -31,7 +31,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import com.google.android.setupcompat.internal.BuildCompat; import com.google.android.setupcompat.internal.LifecycleFragment; import com.google.android.setupcompat.internal.PersistableBundles; import com.google.android.setupcompat.internal.TemplateLayout; @@ -192,7 +191,7 @@ public class PartnerCustomizationLayout extends TemplateLayout { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP - && BuildCompat.isAtLeastQ() + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) { FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); footerBarMixin.onDetachedFromWindow(); @@ -247,7 +246,7 @@ public class PartnerCustomizationLayout extends TemplateLayout { if (!usePartnerResourceAttr) { return false; } - if (!BuildCompat.isAtLeastQ()) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return false; } if (!PartnerConfigHelper.get(getContext()).isAvailable()) { diff --git a/main/java/com/google/android/setupcompat/internal/BuildCompat.java b/main/java/com/google/android/setupcompat/internal/BuildCompat.java deleted file mode 100644 index 7aeb85d..0000000 --- a/main/java/com/google/android/setupcompat/internal/BuildCompat.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.google.android.setupcompat.internal; - -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; - -/** Utility methods for detecting the build API version. */ -public final class BuildCompat { - - private BuildCompat() {} - - // TODO: remove the code for pre-release version of Android Q - public static boolean isAtLeastQ() { - return (VERSION.SDK_INT > VERSION_CODES.P) - || (VERSION.CODENAME.length() == 1 - && VERSION.CODENAME.charAt(0) >= 'Q' - && VERSION.CODENAME.charAt(0) <= 'Z'); - } -} diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java new file mode 100644 index 0000000..6a019fd --- /dev/null +++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java @@ -0,0 +1,155 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.setupcompat.internal; + +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.template.FooterButton; + +/** Keep the partner configuration of a footer button. Used when the button is inflated. */ +public class FooterButtonPartnerConfig { + private final PartnerConfig buttonBackgroundConfig; + private final PartnerConfig buttonIconConfig; + private final PartnerConfig buttonTextColorConfig; + private final PartnerConfig buttonTextSizeConfig; + private final PartnerConfig buttonTextTypeFaceConfig; + private final PartnerConfig buttonRadiusConfig; + private final PartnerConfig buttonRippleColorAlphaConfig; + private final int partnerTheme; + + private FooterButtonPartnerConfig( + int partnerTheme, + PartnerConfig buttonBackgroundConfig, + PartnerConfig buttonIconConfig, + PartnerConfig buttonTextColorConfig, + PartnerConfig buttonTextSizeConfig, + PartnerConfig buttonTextTypeFaceConfig, + PartnerConfig buttonRadiusConfig, + PartnerConfig buttonRippleColorAlphaConfig) { + this.partnerTheme = partnerTheme; + + this.buttonTextColorConfig = buttonTextColorConfig; + this.buttonTextSizeConfig = buttonTextSizeConfig; + this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig; + this.buttonBackgroundConfig = buttonBackgroundConfig; + this.buttonRadiusConfig = buttonRadiusConfig; + this.buttonIconConfig = buttonIconConfig; + this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig; + } + + public int getPartnerTheme() { + return partnerTheme; + } + + public PartnerConfig getButtonBackgroundConfig() { + return buttonBackgroundConfig; + } + + public PartnerConfig getButtonIconConfig() { + return buttonIconConfig; + } + + public PartnerConfig getButtonTextColorConfig() { + return buttonTextColorConfig; + } + + public PartnerConfig getButtonTextSizeConfig() { + return buttonTextSizeConfig; + } + + public PartnerConfig getButtonTextTypeFaceConfig() { + return buttonTextTypeFaceConfig; + } + + public PartnerConfig getButtonRadiusConfig() { + return buttonRadiusConfig; + } + + public PartnerConfig getButtonRippleColorAlphaConfig() { + return buttonRippleColorAlphaConfig; + } + + /** Builder class for constructing {@code FooterButtonPartnerConfig} objects. */ + public static class Builder { + private final FooterButton footerButton; + private PartnerConfig buttonBackgroundConfig = null; + private PartnerConfig buttonIconConfig = null; + private PartnerConfig buttonTextColorConfig = null; + private PartnerConfig buttonTextSizeConfig = null; + private PartnerConfig buttonTextTypeFaceConfig = null; + private PartnerConfig buttonRadiusConfig = null; + private PartnerConfig buttonRippleColorAlphaConfig = null; + private int partnerTheme; + + public Builder(FooterButton footerButton) { + this.footerButton = footerButton; + // default partnerTheme should be the same as footerButton.getTheme(); + this.partnerTheme = this.footerButton.getTheme(); + } + + public Builder setButtonBackgroundConfig(PartnerConfig buttonBackgroundConfig) { + this.buttonBackgroundConfig = buttonBackgroundConfig; + return this; + } + + public Builder setButtonIconConfig(PartnerConfig buttonIconConfig) { + this.buttonIconConfig = buttonIconConfig; + return this; + } + + public Builder setTextColorConfig(PartnerConfig buttonTextColorConfig) { + this.buttonTextColorConfig = buttonTextColorConfig; + return this; + } + + public Builder setTextSizeConfig(PartnerConfig buttonTextSizeConfig) { + this.buttonTextSizeConfig = buttonTextSizeConfig; + return this; + } + + public Builder setTextTypeFaceConfig(PartnerConfig buttonTextTypeFaceConfig) { + this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig; + return this; + } + + public Builder setButtonRadiusConfig(PartnerConfig buttonRadiusConfig) { + this.buttonRadiusConfig = buttonRadiusConfig; + return this; + } + + public Builder setButtonRippleColorAlphaConfig(PartnerConfig buttonRippleColorAlphaConfig) { + this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig; + return this; + } + + public Builder setPartnerTheme(int partnerTheme) { + this.partnerTheme = partnerTheme; + return this; + } + + public FooterButtonPartnerConfig build() { + return new FooterButtonPartnerConfig( + partnerTheme, + buttonBackgroundConfig, + buttonIconConfig, + buttonTextColorConfig, + buttonTextSizeConfig, + buttonTextTypeFaceConfig, + buttonRadiusConfig, + buttonRippleColorAlphaConfig); + } + } +} diff --git a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java index 6d7d17c..1197645 100644 --- a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java +++ b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java @@ -17,8 +17,12 @@ package com.google.android.setupcompat.internal; import android.annotation.TargetApi; +import android.os.BaseBundle; import android.os.Build.VERSION_CODES; +import android.os.Bundle; import android.os.PersistableBundle; +import android.util.ArrayMap; +import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -54,7 +58,89 @@ public final class PersistableBundles { return result; } + /** Returns a {@link Bundle} that contains all the values from {@code persistableBundle}. */ + public static Bundle toBundle(PersistableBundle persistableBundle) { + Bundle bundle = new Bundle(); + bundle.putAll(persistableBundle); + return bundle; + } + + /** + * Returns a {@link PersistableBundle} that contains values from {@code bundle} that are supported + * by the logging API. Un-supported value types are dropped. + */ + public static PersistableBundle fromBundle(Bundle bundle) { + PersistableBundle to = new PersistableBundle(); + ArrayMap<String, Object> map = toMap(bundle); + for (String key : map.keySet()) { + Object value = map.get(key); + if (value instanceof Long) { + to.putLong(key, (Long) value); + } else if (value instanceof Integer) { + to.putInt(key, (Integer) value); + } else if (value instanceof Double) { + to.putDouble(key, (Double) value); + } else if (value instanceof Boolean) { + to.putBoolean(key, (Boolean) value); + } else if (value instanceof String) { + to.putString(key, (String) value); + } else { + throw new AssertionError(String.format("Missing put* for valid data type? = %s", value)); + } + } + return to; + } + + /** Returns {@code true} if {@code left} contains same set of values as {@code right}. */ + public static boolean equals(PersistableBundle left, PersistableBundle right) { + return (left == right) || toMap(left).equals(toMap(right)); + } + + /** Asserts that {@code persistableBundle} contains only supported data types. */ + public static PersistableBundle assertIsValid(PersistableBundle persistableBundle) { + Preconditions.checkNotNull(persistableBundle, "PersistableBundle cannot be null!"); + for (String key : persistableBundle.keySet()) { + Object value = persistableBundle.get(key); + Preconditions.checkArgument( + isSupportedDataType(value), + String.format("Unknown/unsupported data type [%s] for key %s", value, key)); + } + return persistableBundle; + } + + /** + * Returns a new {@link ArrayMap} that contains values from {@code bundle} that are supported by + * the logging API. + */ + private static ArrayMap<String, Object> toMap(BaseBundle baseBundle) { + if (baseBundle == null || baseBundle.isEmpty()) { + return new ArrayMap<>(0); + } + + ArrayMap<String, Object> map = new ArrayMap<>(baseBundle.size()); + for (String key : baseBundle.keySet()) { + Object value = baseBundle.get(key); + if (!isSupportedDataType(value)) { + Log.w(TAG, String.format("Unknown/unsupported data type [%s] for key %s", value, key)); + continue; + } + map.put(key, baseBundle.get(key)); + } + return map; + } + + private static boolean isSupportedDataType(Object value) { + return value instanceof Integer + || value instanceof Long + || value instanceof Double + || value instanceof Float + || value instanceof String + || value instanceof Boolean; + } + private PersistableBundles() { throw new AssertionError("Should not be instantiated"); } + + private static final String TAG = "SetupCompat.PersistBls"; } diff --git a/main/java/com/google/android/setupcompat/logging/CustomEvent.java b/main/java/com/google/android/setupcompat/logging/CustomEvent.java index 18d8f3c..88ac05e 100644 --- a/main/java/com/google/android/setupcompat/logging/CustomEvent.java +++ b/main/java/com/google/android/setupcompat/logging/CustomEvent.java @@ -19,13 +19,15 @@ package com.google.android.setupcompat.logging; import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; import android.annotation.TargetApi; +import android.os.Build; import android.os.Build.VERSION_CODES; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import androidx.annotation.VisibleForTesting; -import com.google.android.setupcompat.internal.BuildCompat; import com.google.android.setupcompat.internal.ClockProvider; +import com.google.android.setupcompat.internal.PersistableBundles; import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.util.ObjectUtils; @@ -37,22 +39,59 @@ import com.google.android.setupcompat.util.ObjectUtils; */ @TargetApi(VERSION_CODES.Q) public final class CustomEvent implements Parcelable { + private static final String BUNDLE_KEY_TIMESTAMP = "CustomEvent_timestamp"; + private static final String BUNDLE_KEY_METRICKEY = "CustomEvent_metricKey"; + private static final String BUNDLE_KEY_BUNDLE_VALUES = "CustomEvent_bundleValues"; + private static final String BUNDLE_KEY_BUNDLE_PII_VALUES = "CustomEvent_pii_bundleValues"; + private static final String BUNDLE_VERSION = "CustomEvent_version"; + private static final int VERSION = 1; /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ public static CustomEvent create( MetricKey metricKey, PersistableBundle bundle, PersistableBundle piiValues) { Preconditions.checkArgument( - BuildCompat.isAtLeastQ(), "The constructor only support on sdk Q or higher"); - return new CustomEvent(ClockProvider.timeInMillis(), metricKey, bundle, piiValues); + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, + "The constructor only support on sdk Q or higher"); + return new CustomEvent( + ClockProvider.timeInMillis(), + metricKey, + // Assert only in factory methods since these methods are directly used by API consumers + // while constructor is used directly only when data is de-serialized from bundle (which + // might have been sent by a client using a newer API) + PersistableBundles.assertIsValid(bundle), + PersistableBundles.assertIsValid(piiValues)); } /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ public static CustomEvent create(MetricKey metricKey, PersistableBundle bundle) { Preconditions.checkArgument( - BuildCompat.isAtLeastQ(), "The constructor only support on sdk Q or higher."); + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, + "The constructor only support on sdk Q or higher."); return create(metricKey, bundle, PersistableBundle.EMPTY); } + /** Converts {@link Bundle} into {@link CustomEvent}. */ + public static CustomEvent toCustomEvent(Bundle bundle) { + return new CustomEvent( + bundle.getLong(BUNDLE_KEY_TIMESTAMP, /* defaultValue= */ Long.MIN_VALUE), + MetricKey.toMetricKey(bundle.getBundle(BUNDLE_KEY_METRICKEY)), + PersistableBundles.fromBundle(bundle.getBundle(BUNDLE_KEY_BUNDLE_VALUES)), + PersistableBundles.fromBundle(bundle.getBundle(BUNDLE_KEY_BUNDLE_PII_VALUES))); + } + + /** Converts {@link CustomEvent} into {@link Bundle}. */ + public static Bundle toBundle(CustomEvent customEvent) { + Preconditions.checkNotNull(customEvent, "CustomEvent cannot be null"); + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_VERSION, VERSION); + bundle.putLong(BUNDLE_KEY_TIMESTAMP, customEvent.timestampMillis()); + bundle.putBundle(BUNDLE_KEY_METRICKEY, MetricKey.fromMetricKey(customEvent.metricKey())); + bundle.putBundle(BUNDLE_KEY_BUNDLE_VALUES, PersistableBundles.toBundle(customEvent.values())); + bundle.putBundle( + BUNDLE_KEY_BUNDLE_PII_VALUES, PersistableBundles.toBundle(customEvent.piiValues())); + return bundle; + } + public static final Creator<CustomEvent> CREATOR = new Creator<CustomEvent>() { @Override @@ -117,8 +156,8 @@ public final class CustomEvent implements Parcelable { CustomEvent that = (CustomEvent) o; return timestampMillis == that.timestampMillis && ObjectUtils.equals(metricKey, that.metricKey) - && ObjectUtils.equals(persistableBundle, that.persistableBundle) - && ObjectUtils.equals(piiValues, that.piiValues); + && PersistableBundles.equals(persistableBundle, that.persistableBundle) + && PersistableBundles.equals(piiValues, that.piiValues); } @Override @@ -152,19 +191,6 @@ public final class CustomEvent implements Parcelable { for (String key : bundle.keySet()) { assertLengthInRange(key, "bundle key", MIN_BUNDLE_KEY_LENGTH, MAX_STR_LENGTH); Object value = bundle.get(key); - boolean valid = false; - for (Class<?> clazz : CUSTOM_EVENT_ALLOWED_DATA_TYPES) { - if (clazz.isInstance(value)) { - valid = true; - break; - } - } - Preconditions.checkArgument( - valid, - String.format( - "Invalid data type for key='%s'. Expected values of type %s, but found [%s].", - key, CUSTOM_EVENT_ALLOWED_DATA_TYPES, value)); - if (value instanceof String) { Preconditions.checkArgument( ((String) value).length() <= MAX_STR_LENGTH, @@ -175,8 +201,6 @@ public final class CustomEvent implements Parcelable { } } - private static final Class<?>[] CUSTOM_EVENT_ALLOWED_DATA_TYPES = - new Class<?>[] {Integer.class, Long.class, Double.class, String.class, Boolean.class}; @VisibleForTesting static final int MAX_STR_LENGTH = 50; @VisibleForTesting static final int MIN_BUNDLE_KEY_LENGTH = 3; } diff --git a/main/java/com/google/android/setupcompat/logging/MetricKey.java b/main/java/com/google/android/setupcompat/logging/MetricKey.java index 8d3692c..125dee9 100644 --- a/main/java/com/google/android/setupcompat/logging/MetricKey.java +++ b/main/java/com/google/android/setupcompat/logging/MetricKey.java @@ -18,6 +18,7 @@ package com.google.android.setupcompat.logging; import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; @@ -31,6 +32,11 @@ import java.util.regex.Pattern; */ public final class MetricKey implements Parcelable { + private static final String METRIC_KEY_BUNDLE_NAME_KEY = "MetricKey_name"; + private static final String METRIC_KEY_BUNDLE_SCREEN_NAME_KEY = "MetricKey_screenName"; + private static final String METRIC_KEY_BUNDLE_VERSION = "MetricKey_version"; + private static final int VERSION = 1; + /** * Creates a new instance of MetricKey. * @@ -56,6 +62,24 @@ public final class MetricKey implements Parcelable { return new MetricKey(name, screenName); } + /** Converts {@link MetricKey} into {@link Bundle}. */ + public static Bundle fromMetricKey(MetricKey metricKey) { + Preconditions.checkNotNull(metricKey, "MetricKey cannot be null."); + Bundle bundle = new Bundle(); + bundle.putInt(METRIC_KEY_BUNDLE_VERSION, VERSION); + bundle.putString(METRIC_KEY_BUNDLE_NAME_KEY, metricKey.name()); + bundle.putString(METRIC_KEY_BUNDLE_SCREEN_NAME_KEY, metricKey.screenName()); + return bundle; + } + + /** Converts {@link Bundle} into {@link MetricKey}. */ + public static MetricKey toMetricKey(Bundle bundle) { + Preconditions.checkNotNull(bundle, "Bundle cannot be null"); + return MetricKey.get( + bundle.getString(METRIC_KEY_BUNDLE_NAME_KEY), + bundle.getString(METRIC_KEY_BUNDLE_SCREEN_NAME_KEY)); + } + public static final Creator<MetricKey> CREATOR = new Creator<MetricKey>() { @Override diff --git a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java index 2869c65..8d696e0 100644 --- a/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java +++ b/main/java/com/google/android/setupcompat/logging/SetupMetricsLogger.java @@ -17,11 +17,10 @@ package com.google.android.setupcompat.logging; import android.content.Context; -import android.os.Bundle; import androidx.annotation.NonNull; import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; -import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricBundleKeys; +import com.google.android.setupcompat.logging.internal.MetricBundleConverter; import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType; import java.util.concurrent.TimeUnit; @@ -32,9 +31,9 @@ public class SetupMetricsLogger { public static void logCustomEvent(@NonNull Context context, @NonNull CustomEvent customEvent) { Preconditions.checkNotNull(context, "Context cannot be null."); Preconditions.checkNotNull(customEvent, "CustomEvent cannot be null."); - Bundle bundle = new Bundle(); - bundle.putParcelable(MetricBundleKeys.CUSTOM_EVENT, customEvent); - SetupCompatServiceInvoker.get(context).logMetricEvent(MetricType.CUSTOM_EVENT, bundle); + SetupCompatServiceInvoker.get(context) + .logMetricEvent( + MetricType.CUSTOM_EVENT, MetricBundleConverter.createBundleForLogging(customEvent)); } /** Increments the counter value with the name {@code counterName} by {@code times}. */ @@ -43,10 +42,10 @@ public class SetupMetricsLogger { Preconditions.checkNotNull(context, "Context cannot be null."); Preconditions.checkNotNull(counterName, "CounterName cannot be null."); Preconditions.checkArgument(times > 0, "Counter cannot be negative."); - Bundle bundle = new Bundle(); - bundle.putParcelable(MetricBundleKeys.METRIC_KEY, counterName); - bundle.putInt(MetricBundleKeys.COUNTER_INT, times); - SetupCompatServiceInvoker.get(context).logMetricEvent(MetricType.COUNTER_EVENT, bundle); + SetupCompatServiceInvoker.get(context) + .logMetricEvent( + MetricType.COUNTER_EVENT, + MetricBundleConverter.createBundleForLoggingCounter(counterName, times)); } /** @@ -67,9 +66,9 @@ public class SetupMetricsLogger { Preconditions.checkNotNull(context, "Context cannot be null."); Preconditions.checkNotNull(timerName, "Timer name cannot be null."); Preconditions.checkArgument(timeInMillis >= 0, "Duration cannot be negative."); - Bundle bundle = new Bundle(); - bundle.putParcelable(MetricBundleKeys.METRIC_KEY, timerName); - bundle.putLong(MetricBundleKeys.TIME_MILLIS_LONG, timeInMillis); - SetupCompatServiceInvoker.get(context).logMetricEvent(MetricType.DURATION_EVENT, bundle); + SetupCompatServiceInvoker.get(context) + .logMetricEvent( + MetricType.DURATION_EVENT, + MetricBundleConverter.createBundleForLoggingTimer(timerName, timeInMillis)); } } diff --git a/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java new file mode 100644 index 0000000..e1a3909 --- /dev/null +++ b/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java @@ -0,0 +1,34 @@ +package com.google.android.setupcompat.logging.internal; + +import android.os.Bundle; +import com.google.android.setupcompat.logging.CustomEvent; +import com.google.android.setupcompat.logging.MetricKey; +import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricBundleKeys; + +/** Collection of helper methods for reading and writing {@link CustomEvent}, {@link MetricKey}. */ +public final class MetricBundleConverter { + + public static Bundle createBundleForLogging(CustomEvent customEvent) { + Bundle bundle = new Bundle(); + bundle.putParcelable(MetricBundleKeys.CUSTOM_EVENT_BUNDLE, CustomEvent.toBundle(customEvent)); + return bundle; + } + + public static Bundle createBundleForLoggingCounter(MetricKey counterName, int times) { + Bundle bundle = new Bundle(); + bundle.putParcelable(MetricBundleKeys.METRIC_KEY_BUNDLE, MetricKey.fromMetricKey(counterName)); + bundle.putInt(MetricBundleKeys.COUNTER_INT, times); + return bundle; + } + + public static Bundle createBundleForLoggingTimer(MetricKey timerName, long timeInMillis) { + Bundle bundle = new Bundle(); + bundle.putParcelable(MetricBundleKeys.METRIC_KEY_BUNDLE, MetricKey.fromMetricKey(timerName)); + bundle.putLong(MetricBundleKeys.TIME_MILLIS_LONG, timeInMillis); + return bundle; + } + + private MetricBundleConverter() { + throw new AssertionError("Cannot instantiate MetricBundleConverter"); + } +} diff --git a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java index 9d70056..57a7272 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java +++ b/main/java/com/google/android/setupcompat/logging/internal/SetupMetricsLoggingConstants.java @@ -55,7 +55,9 @@ public interface SetupMetricsLoggingConstants { @Retention(RetentionPolicy.SOURCE) @StringDef({ MetricBundleKeys.METRIC_KEY, + MetricBundleKeys.METRIC_KEY_BUNDLE, MetricBundleKeys.CUSTOM_EVENT, + MetricBundleKeys.CUSTOM_EVENT_BUNDLE, MetricBundleKeys.TIME_MILLIS_LONG, MetricBundleKeys.COUNTER_INT }) @@ -63,14 +65,18 @@ public interface SetupMetricsLoggingConstants { /** * {@link MetricKey} of the data being logged. This will be set when {@code metricType} is * either {@link MetricType#COUNTER_EVENT} or {@link MetricType#DURATION_EVENT}. + * + * @deprecated Use {@link #METRIC_KEY_BUNDLE} instead. */ - String METRIC_KEY = "MetricKey"; + @Deprecated String METRIC_KEY = "MetricKey"; /** * This key will be used when {@code metricType} is {@link MetricType#CUSTOM_EVENT} with the * value being a parcelable of type {@link com.google.android.setupcompat.logging.CustomEvent}. + * + * @deprecated Use {@link #CUSTOM_EVENT_BUNDLE} instead. */ - String CUSTOM_EVENT = "CustomEvent"; + @Deprecated String CUSTOM_EVENT = "CustomEvent"; /** * This key will be set when {@code metricType} is {@link MetricType#DURATION_EVENT} with the @@ -85,5 +91,18 @@ public interface SetupMetricsLoggingConstants { * MetricKey}. */ String COUNTER_INT = "counter"; + + /** + * {@link MetricKey} of the data being logged. This will be set when {@code metricType} is + * either {@link MetricType#COUNTER_EVENT} or {@link MetricType#DURATION_EVENT}. + */ + String METRIC_KEY_BUNDLE = "MetricKey_bundle"; + + /** + * This key will be used when {@code metricType} is {@link MetricType#CUSTOM_EVENT} with the + * value being a Bundle which can be used to read {@link + * com.google.android.setupcompat.logging.CustomEvent}. + */ + String CUSTOM_EVENT_BUNDLE = "CustomEvent_bundle"; } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index f623382..c931de8 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -35,6 +35,7 @@ import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import androidx.annotation.AttrRes; +import androidx.annotation.CallSuper; import androidx.annotation.ColorInt; import androidx.annotation.IdRes; import androidx.annotation.LayoutRes; @@ -55,7 +56,7 @@ import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import com.google.android.setupcompat.PartnerCustomizationLayout; import com.google.android.setupcompat.R; -import com.google.android.setupcompat.internal.BuildCompat; +import com.google.android.setupcompat.internal.FooterButtonPartnerConfig; import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.logging.internal.FooterBarMixinMetrics; @@ -82,8 +83,10 @@ public class FooterBarMixin implements Mixin { private FooterButton secondaryButton; @IdRes private int primaryButtonId; @IdRes private int secondaryButtonId; - ColorStateList primaryTextColorStateList = null; - ColorStateList secondaryTextColorStateList = null; + ColorStateList primaryDefaultTextColor = null; + ColorStateList secondaryDefaultTextColor = null; + @VisibleForTesting public FooterButtonPartnerConfig primaryButtonPartnerConfigForTesting; + @VisibleForTesting public FooterButtonPartnerConfig secondaryButtonPartnerConfigForTesting; private int footerBarPaddingTop; private int footerBarPaddingBottom; @@ -108,7 +111,11 @@ public class FooterBarMixin implements Mixin { if (button != null) { button.setEnabled(enabled); if (applyPartnerResources && !enabled) { - updateButtonTextColorWithPartnerConfig(button, id == primaryButtonId); + updateButtonTextColorWithPartnerConfig( + button, + (id == primaryButtonId) + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); } } } @@ -203,25 +210,77 @@ public class FooterBarMixin implements Mixin { throw new IllegalStateException("Footer stub is not found in this template"); } buttonContainer = (LinearLayout) inflateFooter(R.layout.suc_footer_button_bar); - if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - buttonContainer.setId(View.generateViewId()); - } else { - buttonContainer.setId(generateViewId()); - } - updateFooterBarPadding(); - if (applyPartnerResources) { - @ColorInt - int color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR); - buttonContainer.setBackgroundColor(color); - } + onFooterBarInflated(buttonContainer); + onFooterBarApplyPartnerResource(buttonContainer); } return buttonContainer; } + /** + * Notifies that the footer bar has been inflated to the view hierarchy. Calling super is + * necessary while subclass implement it. + */ + @CallSuper + protected void onFooterBarInflated(LinearLayout buttonContainer) { + if (buttonContainer == null) { + // Ignore action since buttonContainer is null + return; + } + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + buttonContainer.setId(View.generateViewId()); + } else { + buttonContainer.setId(generateViewId()); + } + updateFooterBarPadding( + buttonContainer, + buttonContainer.getPaddingLeft(), + footerBarPaddingTop, + buttonContainer.getPaddingRight(), + footerBarPaddingBottom); + } + + /** + * Notifies while the footer bar apply Partner Resource. Calling super is necessary while subclass + * implement it. + */ + @CallSuper + protected void onFooterBarApplyPartnerResource(LinearLayout buttonContainer) { + if (buttonContainer == null) { + // Ignore action since buttonContainer is null + return; + } + if (!applyPartnerResources) { + return; + } + + @ColorInt + int color = + PartnerConfigHelper.get(context) + .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR); + buttonContainer.setBackgroundColor(color); + + footerBarPaddingTop = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); + footerBarPaddingBottom = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); + updateFooterBarPadding( + buttonContainer, + buttonContainer.getPaddingLeft(), + footerBarPaddingTop, + buttonContainer.getPaddingRight(), + footerBarPaddingBottom); + } + + /** + * Inflate FooterActionButton with layout "suc_button". Subclasses can implement this method to + * modify the footer button layout as necessary. + */ @SuppressLint("InflateParams") - private FooterActionButton createThemedButton(Context context, @StyleRes int theme) { + protected FooterActionButton createThemedButton(Context context, @StyleRes int theme) { // Inflate a single button from XML, which when using support lib, will take advantage of // the injected layout inflater and give us AppCompatButton instead. LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); @@ -232,41 +291,35 @@ public class FooterBarMixin implements Mixin { @MainThread public void setPrimaryButton(FooterButton footerButton) { ensureOnMainThread("setPrimaryButton"); - LinearLayout buttonContainer = ensureFooterInflated(); - - // Set the default theme if theme is not set, or when running in setup flow. - if (footerButton.getTheme() == 0 || applyPartnerResources) { - footerButton.setTheme(R.style.SucPartnerCustomizationButton_Primary); - } - // TODO: Make sure customize attributes in theme can be applied during setup flow. - // If sets background color to full transparent, the button changes to colored borderless ink - // button style. - if (applyPartnerResources - && PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) - == Color.TRANSPARENT) { - footerButton.setTheme(R.style.SucPartnerCustomizationButton_Secondary); - } - - FooterActionButton button = inflateButton(footerButton); + ensureFooterInflated(); + + // Setup button partner config + FooterButtonPartnerConfig footerButtonPartnerConfig = + new FooterButtonPartnerConfig.Builder(footerButton) + .setPartnerTheme( + getPartnerTheme( + footerButton, + /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary, + /* buttonBackgroundColorConfig= */ PartnerConfig + .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)) + .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) + .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) + .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) + .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) + .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR) + .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) + .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) + .build(); + + FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); + // update information for primary button. Need to update as long as the button inflated. primaryButtonId = button.getId(); - buttonContainer.addView(button); - autoSetButtonBarVisibility(); - - if (applyPartnerResources) { - // This API should only be called after primaryButtonId is set. - updateButtonAttrsWithPartnerConfig(button, true, footerButton.getButtonType()); - } else { - // Try to set default value - if (footerBarPrimaryBackgroundColor != 0) { - updateButtonBackground(button, footerBarPrimaryBackgroundColor); - } else { - // TODO: get button background color from activity theme - } - } - - footerButton.setOnButtonEventListener(createButtonEventListener(primaryButtonId)); + primaryDefaultTextColor = button.getTextColors(); primaryButton = footerButton; + primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; + + onFooterButtonInflated(button, footerBarPrimaryBackgroundColor); + onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); // Make sure the position of buttons are correctly and prevent primary button create twice or // more. @@ -278,7 +331,7 @@ public class FooterBarMixin implements Mixin { return primaryButton; } - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public Button getPrimaryButtonView() { return buttonContainer == null ? null : buttonContainer.findViewById(primaryButtonId); } @@ -292,47 +345,46 @@ public class FooterBarMixin implements Mixin { @MainThread public void setSecondaryButton(FooterButton footerButton) { ensureOnMainThread("setSecondaryButton"); - LinearLayout buttonContainer = ensureFooterInflated(); - - // Set the default theme if theme is not set, or when running in setup flow. - if (footerButton.getTheme() == 0 || applyPartnerResources) { - footerButton.setTheme(R.style.SucPartnerCustomizationButton_Secondary); - } - int color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR); - // TODO: Make sure customize attributes in theme can be applied during setup flow. - // If doesn't set background color to full transparent or white, the button changes to colored - // bordered ink button style. - if (applyPartnerResources && (color != Color.TRANSPARENT && color != Color.WHITE)) { - footerButton.setTheme(R.style.SucPartnerCustomizationButton_Primary); - } - - FooterActionButton button = inflateButton(footerButton); + ensureFooterInflated(); + + // Setup button partner config + FooterButtonPartnerConfig footerButtonPartnerConfig = + new FooterButtonPartnerConfig.Builder(footerButton) + .setPartnerTheme( + getPartnerTheme( + footerButton, + /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Secondary, + /* buttonBackgroundColorConfig= */ PartnerConfig + .CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) + .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) + .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) + .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) + .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) + .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) + .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) + .build(); + + FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); + // update information for secondary button. Need to update as long as the button inflated. secondaryButtonId = button.getId(); - buttonContainer.addView(button); - - if (applyPartnerResources) { - // This API should only be called after secondaryButtonId is set. - updateButtonAttrsWithPartnerConfig(button, false, footerButton.getButtonType()); - } else { - // Try to set default value - if (footerBarSecondaryBackgroundColor != 0) { - updateButtonBackground(button, footerBarSecondaryBackgroundColor); - } else { - // TODO: get button background color from activity theme - } - } - - footerButton.setOnButtonEventListener(createButtonEventListener(secondaryButtonId)); + secondaryDefaultTextColor = button.getTextColors(); secondaryButton = footerButton; + secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; + + onFooterButtonInflated(button, footerBarSecondaryBackgroundColor); + onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); // Make sure the position of buttons are correctly and prevent secondary button create twice or // more. repopulateButtons(); } - private void repopulateButtons() { + /** + * Corrects the order of footer buttons after the button has been inflated to the view hierarchy. + * Subclasses can implement this method to modify the order of footer buttons as necessary. + */ + protected void repopulateButtons() { LinearLayout buttonContainer = ensureFooterInflated(); Button tempPrimaryButton = getPrimaryButtonView(); Button tempSecondaryButton = getSecondaryButtonView(); @@ -347,8 +399,49 @@ public class FooterBarMixin implements Mixin { } } + /** + * Notifies that the footer button has been inInflated and add to the view hierarchy. Calling + * super is necessary while subclass implement it. + */ + @CallSuper + protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) { + // Try to set default background + if (defaultButtonBackgroundColor != 0) { + updateButtonBackground(button, defaultButtonBackgroundColor); + } else { + // TODO: get button background color from activity theme + } + buttonContainer.addView(button); + autoSetButtonBarVisibility(); + } + + private int getPartnerTheme( + FooterButton footerButton, + int defaultPartnerTheme, + PartnerConfig buttonBackgroundColorConfig) { + int overrideTheme = footerButton.getTheme(); + + // Set the default theme if theme is not set, or when running in setup flow. + if (footerButton.getTheme() == 0 || applyPartnerResources) { + overrideTheme = defaultPartnerTheme; + } + // TODO: Make sure customize attributes in theme can be applied during setup flow. + // If sets background color to full transparent, the button changes to colored borderless ink + // button style. + int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig); + if (applyPartnerResources && color == Color.TRANSPARENT) { + overrideTheme = R.style.SucPartnerCustomizationButton_Secondary; + } else if (applyPartnerResources && (color != Color.TRANSPARENT)) { + // TODO: remove the constrain (color != Color.WHITE), need to check all pages go + // well without customization. It should be fine since the default value of secondary bg color + // is set as transparent. + overrideTheme = R.style.SucPartnerCustomizationButton_Primary; + } + return overrideTheme; + } + @VisibleForTesting - LinearLayout getButtonContainer() { + public LinearLayout getButtonContainer() { return buttonContainer; } @@ -392,7 +485,7 @@ public class FooterBarMixin implements Mixin { return buttonContainer.getVisibility(); } - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public Button getSecondaryButtonView() { return buttonContainer == null ? null : buttonContainer.findViewById(secondaryButtonId); } @@ -417,8 +510,10 @@ public class FooterBarMixin implements Mixin { } } - private FooterActionButton inflateButton(FooterButton footerButton) { - FooterActionButton button = createThemedButton(context, footerButton.getTheme()); + private FooterActionButton inflateButton( + FooterButton footerButton, FooterButtonPartnerConfig footerButtonPartnerConfig) { + FooterActionButton button = + createThemedButton(context, footerButtonPartnerConfig.getPartnerTheme()); if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { button.setId(View.generateViewId()); } else { @@ -432,61 +527,57 @@ public class FooterBarMixin implements Mixin { button.setEnabled(footerButton.isEnabled()); button.setFooterButton(footerButton); + footerButton.setOnButtonEventListener(createButtonEventListener(button.getId())); return button; } // TODO: Make sure customize attributes in theme can be applied during setup flow. @TargetApi(VERSION_CODES.Q) - private void updateButtonAttrsWithPartnerConfig( - Button button, boolean isPrimaryButton, @ButtonType int buttonType) { - updateButtonTextColorWithPartnerConfig(button, isPrimaryButton); - updateButtonTextSizeWithPartnerConfig(button); - updateButtonTypeFaceWithPartnerConfig(button); - updateButtonBackgroundWithPartnerConfig(button, isPrimaryButton); - updateButtonRadiusWithPartnerConfig(button); - updateButtonIconWithPartnerConfig(button, buttonType); - updateButtonRippleColorWithPartnerConfig(button, isPrimaryButton); - } - - private void updateButtonTextColorWithPartnerConfig(Button button, boolean isPrimaryButton) { - @ColorInt int color; - if (isPrimaryButton) { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR); - if (primaryTextColorStateList == null) { - primaryTextColorStateList = button.getTextColors(); - } - } else { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); - if (secondaryTextColorStateList == null) { - secondaryTextColorStateList = button.getTextColors(); - } + private void onFooterButtonApplyPartnerResource( + Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) { + if (!applyPartnerResources) { + return; } - + updateButtonTextColorWithPartnerConfig( + button, footerButtonPartnerConfig.getButtonTextColorConfig()); + updateButtonTextSizeWithPartnerConfig( + button, footerButtonPartnerConfig.getButtonTextSizeConfig()); + updateButtonTypeFaceWithPartnerConfig( + button, footerButtonPartnerConfig.getButtonTextTypeFaceConfig()); + updateButtonBackgroundWithPartnerConfig( + button, footerButtonPartnerConfig.getButtonBackgroundConfig()); + updateButtonRadiusWithPartnerConfig(button, footerButtonPartnerConfig.getButtonRadiusConfig()); + updateButtonIconWithPartnerConfig(button, footerButtonPartnerConfig.getButtonIconConfig()); + updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig); + } + + private void updateButtonTextColorWithPartnerConfig( + Button button, PartnerConfig buttonTextColorConfig) { if (button.isEnabled()) { - button.setTextColor(ColorStateList.valueOf(color)); + @ColorInt + int color = PartnerConfigHelper.get(context).getColor(context, buttonTextColorConfig); + if (color != Color.TRANSPARENT) { + button.setTextColor(ColorStateList.valueOf(color)); + } } else { + // disable state will use the default disable state color button.setTextColor( - isPrimaryButton ? primaryTextColorStateList : secondaryTextColorStateList); + button.getId() == primaryButtonId ? primaryDefaultTextColor : secondaryDefaultTextColor); } } - private void updateButtonTextSizeWithPartnerConfig(Button button) { - float size = - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE); + private void updateButtonTextSizeWithPartnerConfig( + Button button, PartnerConfig buttonTextSizeConfig) { + float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig); if (size > 0) { button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } } - private void updateButtonTypeFaceWithPartnerConfig(Button button) { + private void updateButtonTypeFaceWithPartnerConfig( + Button button, PartnerConfig buttonTextTypeFaceConfig) { String fontFamilyName = - PartnerConfigHelper.get(context) - .getString(context, PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY); + PartnerConfigHelper.get(context).getString(context, buttonTextTypeFaceConfig); Typeface font = Typeface.create(fontFamilyName, Typeface.NORMAL); if (font != null) { button.setTypeface(font); @@ -494,21 +585,15 @@ public class FooterBarMixin implements Mixin { } @TargetApi(VERSION_CODES.Q) - private void updateButtonBackgroundWithPartnerConfig(Button button, boolean isPrimaryButton) { + private void updateButtonBackgroundWithPartnerConfig( + Button button, PartnerConfig buttonBackgroundConfig) { Preconditions.checkArgument( - BuildCompat.isAtLeastQ(), "Update button background only support on sdk Q or higher"); + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, + "Update button background only support on sdk Q or higher"); @ColorInt int color; int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled}; int[] ENABLED_STATE_SET = {}; - if (isPrimaryButton) { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR); - } else { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR); - } + color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundConfig); if (color != Color.TRANSPARENT) { TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha}); @@ -537,11 +622,10 @@ public class FooterBarMixin implements Mixin { button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP); } - private void updateButtonRadiusWithPartnerConfig(Button button) { + private void updateButtonRadiusWithPartnerConfig( + Button button, PartnerConfig buttonRadiusConfig) { if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { - float radius = - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS); + float radius = PartnerConfigHelper.get(context).getDimension(context, buttonRadiusConfig); GradientDrawable gradientDrawable = getGradientDrawable(button); if (gradientDrawable != null) { gradientDrawable.setCornerRadius(radius); @@ -549,7 +633,8 @@ public class FooterBarMixin implements Mixin { } } - private void updateButtonRippleColorWithPartnerConfig(Button button, boolean isPrimaryButton) { + private void updateButtonRippleColorWithPartnerConfig( + Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) { // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is // unavailable. Since Stencil customization provider only works on Q+, there is no need to // perform any customization for versions 21. @@ -562,19 +647,13 @@ public class FooterBarMixin implements Mixin { int[] pressedState = {android.R.attr.state_pressed}; @ColorInt int color; // Get partner text color. - if (isPrimaryButton) { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR); - } else { - color = - PartnerConfigHelper.get(context) - .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); - } + color = + PartnerConfigHelper.get(context) + .getColor(context, footerButtonPartnerConfig.getButtonTextColorConfig()); float alpha = PartnerConfigHelper.get(context) - .getFraction(context, PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA); + .getFraction(context, footerButtonPartnerConfig.getButtonRippleColorAlphaConfig()); // Set text color for ripple. ColorStateList colorStateList = @@ -585,14 +664,13 @@ public class FooterBarMixin implements Mixin { } } - private void updateButtonIconWithPartnerConfig(Button button, @ButtonType int buttonType) { + private void updateButtonIconWithPartnerConfig(Button button, PartnerConfig buttonIconConfig) { if (button == null) { return; } Drawable icon = null; - PartnerConfig id = getDrawablePartnerConfig(buttonType); - if (id != null) { - icon = PartnerConfigHelper.get(context).getDrawable(context, id); + if (buttonIconConfig != null) { + icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig); } setButtonIcon(button, icon); } @@ -706,27 +784,13 @@ public class FooterBarMixin implements Mixin { return footerStub.inflate(); } - private void updateFooterBarPadding() { + private void updateFooterBarPadding( + LinearLayout buttonContainer, int left, int top, int right, int bottom) { if (buttonContainer == null) { // Ignore action since buttonContainer is null return; } - - if (applyPartnerResources) { - footerBarPaddingTop = - (int) - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); - footerBarPaddingBottom = - (int) - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); - } - buttonContainer.setPadding( - buttonContainer.getPaddingLeft(), - footerBarPaddingTop, - buttonContainer.getPaddingRight(), - footerBarPaddingBottom); + buttonContainer.setPadding(left, top, right, bottom); } /** Returns the paddingTop of footer bar. */ diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java index 457a116..a4d2c87 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterButton.java @@ -202,16 +202,6 @@ public final class FooterButton implements OnClickListener { void onTextChanged(CharSequence text); } - /** - * Sets the default theme for footer button, the method only for internal use in {@link - * FooterBarMixin} and there will have no influence during setup wizard flow. - * - * @param theme The theme for footer button. - */ - public void setTheme(@StyleRes int theme) { - this.theme = theme; - } - /** Maximum valid value of ButtonType */ private static final int MAX_BUTTON_TYPE = 8; diff --git a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java index 9996128..1bd6949 100644 --- a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java @@ -78,20 +78,20 @@ public class StatusBarMixin implements Mixin { decorView = window.getDecorView(); - // Override the color of status bar to transparent such that the color of - // StatusBarBackgroundLayout can be seen. - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + // Support updating system status bar background color and is light system status bar from M. + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { + // Override the color of status bar to transparent such that the color of + // StatusBarBackgroundLayout can be seen. window.setStatusBarColor(Color.TRANSPARENT); + TypedArray a = + partnerCustomizationLayout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SucStatusBarMixin, defStyleAttr, 0); + setLightStatusBar( + a.getBoolean(R.styleable.SucStatusBarMixin_sucLightStatusBar, isLightStatusBar())); + setStatusBarBackground(a.getDrawable(R.styleable.SucStatusBarMixin_sucStatusBarBackground)); + a.recycle(); } - - TypedArray a = - partnerCustomizationLayout - .getContext() - .obtainStyledAttributes(attrs, R.styleable.SucStatusBarMixin, defStyleAttr, 0); - setLightStatusBar( - a.getBoolean(R.styleable.SucStatusBarMixin_sucLightStatusBar, isLightStatusBar())); - setStatusBarBackground(a.getDrawable(R.styleable.SucStatusBarMixin_sucStatusBarBackground)); - a.recycle(); } /** diff --git a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java index a3ce567..e055d28 100644 --- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java @@ -70,16 +70,21 @@ public class SystemNavBarMixin implements Mixin { * @param defStyleAttr The default style attribute as given to the constructor of the layout. */ public void applyPartnerCustomizations(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { - TypedArray a = - templateLayout - .getContext() - .obtainStyledAttributes(attrs, R.styleable.SucSystemNavBarMixin, defStyleAttr, 0); - sucSystemNavBarBackgroundColor = - a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarBackgroundColor, 0); - setSystemNavBarBackground(sucSystemNavBarBackgroundColor); - setLightSystemNavBar( - a.getBoolean(R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar())); - a.recycle(); + // Support updating system navigation bar background color and is light system navigation bar + // from O. + if (Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1) { + TypedArray a = + templateLayout + .getContext() + .obtainStyledAttributes(attrs, R.styleable.SucSystemNavBarMixin, defStyleAttr, 0); + sucSystemNavBarBackgroundColor = + a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarBackgroundColor, 0); + setSystemNavBarBackground(sucSystemNavBarBackgroundColor); + setLightSystemNavBar( + a.getBoolean( + R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar())); + a.recycle(); + } } /** @@ -194,20 +199,23 @@ public class SystemNavBarMixin implements Mixin { window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarColor(partnerNavigationBarColor); } else { + // noinspection AndroidLintInlinedApi + TypedArray typedArray = + context.obtainStyledAttributes( + new int[] {android.R.attr.statusBarColor, android.R.attr.navigationBarColor}); + int statusBarColor = typedArray.getColor(0, 0); + int navigationBarColor = typedArray.getColor(1, 0); if (templateLayout instanceof PartnerCustomizationLayout) { - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(sucSystemNavBarBackgroundColor); - } else { - // noinspection AndroidLintInlinedApi - final TypedArray typedArray = - context.obtainStyledAttributes( - new int[] {android.R.attr.statusBarColor, android.R.attr.navigationBarColor}); - final int statusBarColor = typedArray.getColor(0, 0); - final int navigationBarColor = typedArray.getColor(1, 0); - window.setStatusBarColor(statusBarColor); - window.setNavigationBarColor(navigationBarColor); - typedArray.recycle(); + if (VERSION.SDK_INT >= VERSION_CODES.M) { + statusBarColor = Color.TRANSPARENT; + } + if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { + navigationBarColor = sucSystemNavBarBackgroundColor; + } } + window.setStatusBarColor(statusBarColor); + window.setNavigationBarColor(navigationBarColor); + typedArray.recycle(); } } } diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 61cd760..3896731 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -19,12 +19,12 @@ package com.google.android.setupcompat.util; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.Settings; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.google.android.setupcompat.internal.BuildCompat; import java.util.Arrays; /** @@ -202,7 +202,7 @@ public class WizardManagerHelper { return false; } - if (BuildCompat.isAtLeastQ()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return originalIntent.getBooleanExtra(EXTRA_IS_SETUP_FLOW, false); } else { return isSetupWizardIntent(originalIntent) diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml index e336905..1a5342c 100644 --- a/main/res/values/attrs.xml +++ b/main/res/values/attrs.xml @@ -34,7 +34,7 @@ <attr name="sucUsePartnerResource" format="boolean" /> </declare-styleable> - <!-- Status bar attributes --> + <!-- Status bar attributes; only takes effect on M or above --> <declare-styleable name="SucStatusBarMixin"> <!-- The color for the status bar. For this to take effect, "android:windowDrawsSystemBarBackgrounds" should be set to true and @@ -45,7 +45,7 @@ <attr name="sucLightStatusBar" format="boolean" /> </declare-styleable> - <!-- System navigation bar attributes --> + <!-- System navigation bar attributes; only takes effect on O_MR1 or above --> <declare-styleable name="SucSystemNavBarMixin"> <!-- The color for the system navigation bar. For this to take effect, "android:windowDrawsSystemBarBackgrounds" should be set to true and |