summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSetup Wizard Team <android-setup-team-eng@google.com>2019-12-31 20:58:11 +0800
committerpastychang <pastychang@google.com>2019-01-03 10:44:14 +0800
commitd6fc4afb2ea521d09c0ab12b171ef56a854ecdf9 (patch)
tree85d4fd5e34657ffc2d96a6c079610a72f1624ee7
parentff4fecb9b05c27aef05f1acbe9cb2524062df65d (diff)
downloadsetupcompat-d6fc4afb2ea521d09c0ab12b171ef56a854ecdf9.tar.gz
Import updated Android SetupCompat Library 227351086
Test: mm PiperOrigin-RevId: 227351086 Change-Id: I50a18bf0fc7fe01569098838edee43384bd8f6a0
-rw-r--r--main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java10
-rw-r--r--main/java/com/google/android/setupcompat/internal/PersistableBundles.java43
-rw-r--r--main/java/com/google/android/setupcompat/internal/Validations.java54
-rw-r--r--main/java/com/google/android/setupcompat/item/FooterButton.java59
-rw-r--r--main/java/com/google/android/setupcompat/logging/CustomEvent.java39
-rw-r--r--main/java/com/google/android/setupcompat/logging/MetricKey.java22
-rw-r--r--main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java37
-rw-r--r--main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java72
-rw-r--r--main/java/com/google/android/setupcompat/template/ColoredHeaderMixin.java70
-rw-r--r--main/java/com/google/android/setupcompat/template/HeaderMixin.java92
-rw-r--r--main/java/com/google/android/setupcompat/template/IconMixin.java109
-rw-r--r--main/java/com/google/android/setupcompat/util/PartnerConfig.java25
-rw-r--r--main/java/com/google/android/setupcompat/util/PartnerConfigKey.java27
-rw-r--r--main/res/layout/partner_customization_layout.xml12
-rw-r--r--main/res/values/attrs.xml28
-rw-r--r--main/res/values/dimens.xml3
-rw-r--r--main/res/values/styles.xml8
17 files changed, 621 insertions, 89 deletions
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index 7d0e816..fa4273f 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -31,6 +31,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.WindowManager;
+import com.google.android.setupcompat.internal.PersistableBundles;
import com.google.android.setupcompat.lifecycle.LifecycleFragment;
import com.google.android.setupcompat.logging.CustomEvent;
import com.google.android.setupcompat.logging.MetricKey;
@@ -43,8 +44,7 @@ import com.google.android.setupcompat.util.WizardManagerHelper;
/** A templatization layout with consistent style used in Setup Wizard or app itself. */
public class PartnerCustomizationLayout extends TemplateLayout {
- private final boolean suwVersionSupportPartnerResource =
- Build.VERSION.SDK_INT > VERSION_CODES.P;
+ private final boolean suwVersionSupportPartnerResource = Build.VERSION.SDK_INT > VERSION_CODES.P;
private Activity activity;
public PartnerCustomizationLayout(Context context) {
@@ -147,9 +147,9 @@ public class PartnerCustomizationLayout extends TemplateLayout {
if (WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
ButtonFooterMixin buttonFooterMixin = getMixin(ButtonFooterMixin.class);
buttonFooterMixin.onDetachedFromWindow();
- PersistableBundle persistableBundle = new PersistableBundle();
- persistableBundle.putPersistableBundle(
- "FooterButtonVisibilityMetrics", buttonFooterMixin.getLoggingMetrics());
+ PersistableBundle persistableBundle =
+ PersistableBundles.mergeBundles(
+ new PersistableBundle(), buttonFooterMixin.getLoggingMetrics());
SetupMetricsLogger.logCustomEvent(
getContext(),
CustomEvent.create(
diff --git a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
new file mode 100644
index 0000000..6fce61f
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
@@ -0,0 +1,43 @@
+package com.google.android.setupcompat.internal;
+
+import android.os.PersistableBundle;
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Contains utility methods related to {@link PersistableBundle}. */
+public final class PersistableBundles {
+
+ /**
+ * Merges two or more {@link PersistableBundle}. Ensures no conflict of keys occurred during
+ * merge.
+ *
+ * @return Returns a new {@link PersistableBundle} that contains all the data from {@code
+ * firstBundle}, {@code nextBundle} and {@code others}.
+ */
+ public static PersistableBundle mergeBundles(
+ PersistableBundle firstBundle, PersistableBundle nextBundle, PersistableBundle... others) {
+ List<PersistableBundle> allBundles = new ArrayList<>();
+ allBundles.addAll(Arrays.asList(firstBundle, nextBundle));
+ Collections.addAll(allBundles, others);
+
+ PersistableBundle result = new PersistableBundle();
+ for (PersistableBundle bundle : allBundles) {
+ for (String key : bundle.keySet()) {
+ Preconditions.checkArgument(
+ !result.containsKey(key),
+ "Found duplicate key [%s] while attempting to merge bundles.",
+ key);
+ }
+ result.putAll(bundle);
+ }
+
+ return result;
+ }
+
+ private PersistableBundles() {
+ throw new AssertionError("Should not be instantiated");
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/internal/Validations.java b/main/java/com/google/android/setupcompat/internal/Validations.java
new file mode 100644
index 0000000..584cad9
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/internal/Validations.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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.common.base.Preconditions;
+
+/** Commonly used validations and preconditions. */
+public final class Validations {
+
+ /**
+ * Asserts that the {@code length} is in the expected range.
+ *
+ * @throws IllegalArgumentException if {@code input}'s length is than {@code minLength} or
+ * greather than {@code maxLength}.
+ */
+ public static void assertLengthInRange(int length, String name, int minLength, int maxLength) {
+ Preconditions.checkArgument(
+ length <= maxLength && length >= minLength,
+ "Length of %s should be in the range [%s-%s]",
+ name,
+ minLength,
+ maxLength);
+ }
+
+ /**
+ * Asserts that the {@code input}'s length is in the expected range.
+ *
+ * @throws NullPointerException if {@code input} is null.
+ * @throws IllegalArgumentException if {@code input}'s length is than {@code minLength} or
+ * greather than {@code maxLength}.
+ */
+ public static void assertLengthInRange(String input, String name, int minLength, int maxLength) {
+ Preconditions.checkNotNull(input, "%s cannot be null.", name);
+ assertLengthInRange(input.length(), name, minLength, maxLength);
+ }
+
+ private Validations() {
+ throw new AssertionError("Should not be instantiated");
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/item/FooterButton.java b/main/java/com/google/android/setupcompat/item/FooterButton.java
index dbebc90..1b32449 100644
--- a/main/java/com/google/android/setupcompat/item/FooterButton.java
+++ b/main/java/com/google/android/setupcompat/item/FooterButton.java
@@ -35,7 +35,6 @@ import com.google.android.setupcompat.template.ButtonFooterMixin;
* button type and click listener, and ButtonFooterMixin will inflate a corresponding Button view.
*/
public final class FooterButton {
- private static final int BUTTON_TYPE_NONE = 0;
private final ButtonType buttonType;
private CharSequence text;
@@ -52,7 +51,9 @@ public final class FooterButton {
onClickListener = null;
buttonType =
ButtonType.valueOf(
- a.getInt(R.styleable.SucFooterButton_sucButtonType, /* defValue= */ BUTTON_TYPE_NONE));
+ a.getInt(
+ R.styleable.SucFooterButton_sucButtonType,
+ /* defValue= */ ButtonType.OTHER.getEnumValue()));
theme = a.getResourceId(R.styleable.SucFooterButton_android_theme, /* defValue= */ 0);
a.recycle();
}
@@ -164,6 +165,7 @@ public final class FooterButton {
* @param enabled True if this view is enabled, false otherwise.
*/
public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
if (buttonListener != null && id != 0) {
buttonListener.onEnabledChanged(enabled, id);
}
@@ -242,27 +244,56 @@ public final class FooterButton {
this.theme = theme;
}
- /** Types for footer button. The button appearance and behavior may change based on its type. */
+ /**
+ * Types for footer button. The button appearance and behavior may change based on its type. In
+ * order to be backward compatible with application built with old version of setupcompat; the
+ * {@code enumValue} of each ButtonType should not be changed.
+ */
public enum ButtonType {
/** A type of button that doesn't fit into any other categories. */
- OTHER,
+ OTHER(0),
+ /**
+ * A type of button that will set up additional elements of the ongoing setup step(s) when
+ * clicked.
+ */
+ ADD_ANOTHER(1),
+ /** A type of button that will cancel the ongoing setup step(s) and exit setup when clicked. */
+ CANCEL(2),
+ /** A type of button that will clear the progress when clicked. (eg: clear PIN code) */
+ CLEAR(3),
+ /** A type of button that will exit the setup flow when clicked. */
+ DONE(4),
/** A type of button that will go to the next screen, or next step in the flow when clicked. */
- NEXT,
+ NEXT(5),
+ /** A type of button to opt-in or agree to the features described in the current screen. */
+ OPT_IN(6),
/** A type of button that will skip the current step when clicked. */
- SKIP,
- /** A type of button that will cancel the ongoing setup step(s) and exit setup when clicked. */
- CANCEL,
+ SKIP(7),
/** A type of button that will stop the ongoing setup step(s) and skip forward when clicked. */
- STOP;
+ STOP(8);
+
+ private final int enumValue;
+
+ public int getEnumValue() {
+ return enumValue;
+ }
+
+ ButtonType(int enumValue) {
+ this.enumValue = enumValue;
+ }
- public static ButtonType valueOf(int value) {
- if (value >= 0 && value < ButtonType.values().length) {
- return ButtonType.values()[value];
- } else {
- return OTHER;
+ public static ButtonType valueOf(int enumValue) {
+ for (ButtonType type : ButtonType.values()) {
+ if (type.getEnumValue() == enumValue) {
+ return type;
+ }
}
+ return OTHER;
}
}
+ // LINT.ThenChange(
+ // //depot/google3/third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java,
+ // //depot/google3/third_party/java_src/android_libs/setupcompat/main/res/values/attrs.xml)
/**
* Builder class for constructing {@code FooterButton} objects.
diff --git a/main/java/com/google/android/setupcompat/logging/CustomEvent.java b/main/java/com/google/android/setupcompat/logging/CustomEvent.java
index 363c3ef..f9aa3b9 100644
--- a/main/java/com/google/android/setupcompat/logging/CustomEvent.java
+++ b/main/java/com/google/android/setupcompat/logging/CustomEvent.java
@@ -16,9 +16,12 @@
package com.google.android.setupcompat.logging;
+import static com.google.android.setupcompat.internal.Validations.assertLengthInRange;
+
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import androidx.annotation.VisibleForTesting;
import com.google.android.setupcompat.internal.ClockProvider;
import com.google.common.base.Preconditions;
import java.util.Objects;
@@ -71,7 +74,7 @@ public final class CustomEvent implements Parcelable {
/** Returns the non PII values describing the event. Only primitive values are supported. */
public PersistableBundle values() {
- return this.persistableBundle;
+ return new PersistableBundle(this.persistableBundle);
}
/**
@@ -125,6 +128,7 @@ public final class CustomEvent implements Parcelable {
Preconditions.checkNotNull(bundle, "Bundle cannot be null.");
Preconditions.checkArgument(!bundle.isEmpty(), "Bundle cannot be empty.");
Preconditions.checkNotNull(piiValues, "piiValues cannot be null.");
+ assertPersistableBundleIsValid(bundle);
this.timestampMillis = timestampMillis;
this.metricKey = metricKey;
this.persistableBundle = bundle.deepCopy();
@@ -135,4 +139,37 @@ public final class CustomEvent implements Parcelable {
private final MetricKey metricKey;
private final PersistableBundle persistableBundle;
private final PersistableBundle piiValues;
+
+ private static void assertPersistableBundleIsValid(PersistableBundle bundle) {
+ 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,
+ "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,
+ "Maximum length of string value for key='%s' cannot exceed %s.",
+ key,
+ MAX_STR_LENGTH);
+ }
+ }
+ }
+
+ 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 a74a2a6..fe310f2 100644
--- a/main/java/com/google/android/setupcompat/logging/MetricKey.java
+++ b/main/java/com/google/android/setupcompat/logging/MetricKey.java
@@ -16,6 +16,8 @@
package com.google.android.setupcompat.logging;
+import static com.google.android.setupcompat.internal.Validations.assertLengthInRange;
+
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
@@ -42,15 +44,9 @@ public final class MetricKey implements Parcelable {
* </ul>
*/
public static MetricKey get(@NonNull String name, @NonNull String screenName) {
- Preconditions.checkNotNull(name);
- Preconditions.checkNotNull(screenName);
- assertLengthInRange(
- name.length(), "MetricKey.name", MIN_METRIC_KEY_LENGTH, MAX_METRIC_KEY_LENGTH);
+ assertLengthInRange(name, "MetricKey.name", MIN_METRIC_KEY_LENGTH, MAX_METRIC_KEY_LENGTH);
assertLengthInRange(
- screenName.length(),
- "MetricKey.screenName",
- MIN_SCREEN_NAME_LENGTH,
- MAX_SCREEN_NAME_LENGTH);
+ screenName, "MetricKey.screenName", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH);
Preconditions.checkArgument(
METRIC_KEY_PATTERN.matcher(name).matches(),
"Invalid MetricKey, only alpha numeric characters are allowed.");
@@ -119,16 +115,6 @@ public final class MetricKey implements Parcelable {
private final String name;
private final String screenName;
- private static void assertLengthInRange(
- int foundLength, String name, int minLength, int maxLength) {
- Preconditions.checkArgument(
- foundLength <= maxLength && foundLength >= minLength,
- "Length of %s should be in the range [%s-%s]",
- name,
- minLength,
- maxLength);
- }
-
private static final int MIN_SCREEN_NAME_LENGTH = 5;
private static final int MIN_METRIC_KEY_LENGTH = 5;
private static final int MAX_SCREEN_NAME_LENGTH = 50;
diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
new file mode 100644
index 0000000..5ca8ed1
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
@@ -0,0 +1,37 @@
+package com.google.android.setupcompat.logging.internal;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.logging.CustomEvent;
+import com.google.android.setupcompat.logging.MetricKey;
+import com.google.android.setupcompat.logging.SetupMetricsLogger;
+
+/** Uses to log internal event customization resource list. */
+public class PartnerCustomizedResourceListMetric {
+
+ public static void logMetrics(Context context, String screenName, Bundle bundle) {
+ PersistableBundle logBundle =
+ buildLogBundleFromResourceConfigBundle(context.getPackageName(), bundle);
+ if (!logBundle.isEmpty()) {
+ SetupMetricsLogger.logCustomEvent(
+ context,
+ CustomEvent.create(MetricKey.get("PartnerCustomizationResource", screenName), logBundle));
+ }
+ }
+
+ @VisibleForTesting
+ public static PersistableBundle buildLogBundleFromResourceConfigBundle(
+ String defaultPackageName, Bundle resourceConfigBundle) {
+ PersistableBundle persistableBundle = new PersistableBundle();
+ for (String key : resourceConfigBundle.keySet()) {
+ Bundle resourceExtra = resourceConfigBundle.getBundle(key);
+ if (!resourceExtra.getString("packageName", defaultPackageName).equals(defaultPackageName)) {
+ persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), true);
+ }
+ }
+
+ return persistableBundle;
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java b/main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java
index 47177af..4aca790 100644
--- a/main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java
+++ b/main/java/com/google/android/setupcompat/template/ButtonFooterMixin.java
@@ -81,6 +81,7 @@ public class ButtonFooterMixin implements Mixin {
private int footerBarPaddingTop;
private int footerBarPaddingBottom;
+ @VisibleForTesting int defaultPadding;
private static final AtomicInteger nextGeneratedId = new AtomicInteger(1);
@@ -157,11 +158,9 @@ public class ButtonFooterMixin implements Mixin {
footerStub = (ViewStub) layout.findManagedViewById(R.id.suc_layout_footer);
this.applyPartnerResources = applyPartnerResources;
- int defaultPadding =
- context
- .getResources()
- .getDimensionPixelSize(R.dimen.suc_customization_footer_padding_vertical);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SucFooterBar, defStyleAttr, 0);
+ defaultPadding =
+ a.getDimensionPixelSize(R.styleable.SucFooterBar_sucFooterButtonPaddingVertical, 0);
footerBarPaddingTop =
a.getDimensionPixelSize(R.styleable.SucFooterBar_sucFooterBarPaddingTop, defaultPadding);
footerBarPaddingBottom =
@@ -476,32 +475,10 @@ public class ButtonFooterMixin implements Mixin {
if (button == null) {
return;
}
- Drawable icon;
- switch (buttonType) {
- case NEXT:
- icon =
- PartnerConfigHelper.get(context)
- .getDrawable(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_NEXT);
- break;
- case SKIP:
- icon =
- PartnerConfigHelper.get(context)
- .getDrawable(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_SKIP);
- break;
- case CANCEL:
- icon =
- PartnerConfigHelper.get(context)
- .getDrawable(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CANCEL);
- break;
- case STOP:
- icon =
- PartnerConfigHelper.get(context)
- .getDrawable(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_STOP);
- break;
- case OTHER:
- default:
- icon = null;
- break;
+ Drawable icon = null;
+ PartnerConfig id = getDrawablePartnerConfig(buttonType);
+ if (id != null) {
+ icon = PartnerConfigHelper.get(context).getDrawable(context, id);
}
setButtonIcon(button, icon);
}
@@ -532,6 +509,41 @@ public class ButtonFooterMixin implements Mixin {
}
}
+ private static PartnerConfig getDrawablePartnerConfig(ButtonType buttonType) {
+ PartnerConfig result;
+ switch (buttonType) {
+ case ADD_ANOTHER:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER;
+ break;
+ case CANCEL:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CANCEL;
+ break;
+ case CLEAR:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CLEAR;
+ break;
+ case DONE:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_DONE;
+ break;
+ case NEXT:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_NEXT;
+ break;
+ case OPT_IN:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_OPT_IN;
+ break;
+ case SKIP:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_SKIP;
+ break;
+ case STOP:
+ result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_STOP;
+ break;
+ case OTHER:
+ default:
+ result = null;
+ break;
+ }
+ return result;
+ }
+
GradientDrawable getGradientDrawable(Button button) {
Drawable drawable = button.getBackground();
if (drawable instanceof InsetDrawable) {
diff --git a/main/java/com/google/android/setupcompat/template/ColoredHeaderMixin.java b/main/java/com/google/android/setupcompat/template/ColoredHeaderMixin.java
new file mode 100644
index 0000000..85b7825
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/ColoredHeaderMixin.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.template;
+
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.TextView;
+import com.google.android.setupcompat.R;
+import com.google.android.setupcompat.TemplateLayout;
+
+/**
+ * A {@link Mixin} displaying a header text that can be set to different colors. This Mixin is
+ * registered to the template using HeaderMixin.class, and can be retrieved using: {@code
+ * (ColoredHeaderMixin) templateLayout.getMixin(HeaderMixin.class}.
+ */
+public class ColoredHeaderMixin extends HeaderMixin {
+
+ /** {@inheritDoc} */
+ public ColoredHeaderMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) {
+ super(layout, attrs, defStyleAttr);
+
+ final TypedArray a =
+ layout
+ .getContext()
+ .obtainStyledAttributes(attrs, R.styleable.SucColoredHeaderMixin, defStyleAttr, 0);
+
+ // Set the header color
+ final ColorStateList headerColor =
+ a.getColorStateList(R.styleable.SucColoredHeaderMixin_sucHeaderColor);
+ if (headerColor != null) {
+ setColor(headerColor);
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Sets the color of the header text. This can also be set via XML using {@code
+ * app:sucHeaderColor}.
+ *
+ * @param color The text color of the header.
+ */
+ public void setColor(ColorStateList color) {
+ final TextView titleView = getTextView();
+ if (titleView != null) {
+ titleView.setTextColor(color);
+ }
+ }
+
+ /** @return The current text color of the header. */
+ public ColorStateList getColor() {
+ final TextView titleView = getTextView();
+ return titleView != null ? titleView.getTextColors() : null;
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/template/HeaderMixin.java b/main/java/com/google/android/setupcompat/template/HeaderMixin.java
new file mode 100644
index 0000000..b5d06ec
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/HeaderMixin.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 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.template;
+
+import android.content.res.TypedArray;
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.TextView;
+import com.google.android.setupcompat.R;
+import com.google.android.setupcompat.TemplateLayout;
+
+/**
+ * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the header text.
+ */
+public class HeaderMixin implements Mixin {
+
+ private final TemplateLayout templateLayout;
+
+ /**
+ * @param layout The layout this Mixin belongs to.
+ * @param attrs XML attributes given to the layout.
+ * @param defStyleAttr The default style attribute as given to the constructor of the layout.
+ */
+ public HeaderMixin(
+ @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
+ templateLayout = layout;
+
+ final TypedArray a =
+ layout
+ .getContext()
+ .obtainStyledAttributes(attrs, R.styleable.SucHeaderMixin, defStyleAttr, 0);
+
+ // Set the header text
+ final CharSequence headerText = a.getText(R.styleable.SucHeaderMixin_sucHeaderText);
+ if (headerText != null) {
+ setText(headerText);
+ }
+
+ a.recycle();
+ }
+
+ /** @return The TextView displaying the header. */
+ public TextView getTextView() {
+ return (TextView) templateLayout.findManagedViewById(R.id.suc_layout_title);
+ }
+
+ /**
+ * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}.
+ *
+ * @param title The resource ID of the text to be set as header.
+ */
+ public void setText(int title) {
+ final TextView titleView = getTextView();
+ if (titleView != null) {
+ titleView.setText(title);
+ }
+ }
+
+ /**
+ * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}.
+ *
+ * @param title The text to be set as header.
+ */
+ public void setText(CharSequence title) {
+ final TextView titleView = getTextView();
+ if (titleView != null) {
+ titleView.setText(title);
+ }
+ }
+
+ /** @return The current header text. */
+ public CharSequence getText() {
+ final TextView titleView = getTextView();
+ return titleView != null ? titleView.getText() : null;
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/template/IconMixin.java b/main/java/com/google/android/setupcompat/template/IconMixin.java
new file mode 100644
index 0000000..c319277
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/IconMixin.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.template;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.DrawableRes;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import com.google.android.setupcompat.R;
+import com.google.android.setupcompat.TemplateLayout;
+
+/**
+ * A {@link com.google.android.setupcompat.template.Mixin} for setting an icon on the template
+ * layout.
+ */
+public class IconMixin implements Mixin {
+
+ private final TemplateLayout templateLayout;
+
+ /**
+ * @param layout The template layout that this Mixin is a part of.
+ * @param attrs XML attributes given to the layout.
+ * @param defStyleAttr The default style attribute as given to the constructor of the layout.
+ */
+ public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) {
+ templateLayout = layout;
+ final Context context = layout.getContext();
+
+ final TypedArray a =
+ context.obtainStyledAttributes(attrs, R.styleable.SucIconMixin, defStyleAttr, 0);
+
+ final @DrawableRes int icon = a.getResourceId(R.styleable.SucIconMixin_android_icon, 0);
+ if (icon != 0) {
+ setIcon(icon);
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}.
+ *
+ * @param icon A drawable icon.
+ */
+ public void setIcon(Drawable icon) {
+ final ImageView iconView = getView();
+ if (iconView != null) {
+ iconView.setImageDrawable(icon);
+ iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ /**
+ * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}.
+ *
+ * @param icon A drawable icon resource.
+ */
+ public void setIcon(@DrawableRes int icon) {
+ final ImageView iconView = getView();
+ if (iconView != null) {
+ // Note: setImageResource on the ImageView is overridden in AppCompatImageView for
+ // support lib users, which enables vector drawable compat to work on versions pre-L.
+ iconView.setImageResource(icon);
+ iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ /** @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} */
+ public Drawable getIcon() {
+ final ImageView iconView = getView();
+ return iconView != null ? iconView.getDrawable() : null;
+ }
+
+ /** Sets the content description of the icon view */
+ public void setContentDescription(CharSequence description) {
+ final ImageView iconView = getView();
+ if (iconView != null) {
+ iconView.setContentDescription(description);
+ }
+ }
+
+ /** @return The content description of the icon view */
+ public CharSequence getContentDescription() {
+ final ImageView iconView = getView();
+ return iconView != null ? iconView.getContentDescription() : null;
+ }
+
+ /** @return The ImageView responsible for displaying the icon. */
+ protected ImageView getView() {
+ return (ImageView) templateLayout.findManagedViewById(R.id.suc_layout_icon);
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/util/PartnerConfig.java b/main/java/com/google/android/setupcompat/util/PartnerConfig.java
index 083479d..f892524 100644
--- a/main/java/com/google/android/setupcompat/util/PartnerConfig.java
+++ b/main/java/com/google/android/setupcompat/util/PartnerConfig.java
@@ -16,6 +16,7 @@
package com.google.android.setupcompat.util;
+// TODO(b/121371322): optimize the enum
/** Resources that can be customized by partner overlay APK. */
public enum PartnerConfig {
@@ -39,18 +40,34 @@ public enum PartnerConfig {
CONFIG_FOOTER_BUTTON_FONT_FAMILY(
PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY, ResourceType.STRING),
+ // The icon for "add another" action. Can be "@null" for no icon.
+ CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER, ResourceType.DRAWABLE),
+
+ // The icon for "cancel" action. Can be "@null" for no icon.
+ CONFIG_FOOTER_BUTTON_ICON_CANCEL(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, ResourceType.DRAWABLE),
+
+ // The icon for "clear" action. Can be "@null" for no icon.
+ CONFIG_FOOTER_BUTTON_ICON_CLEAR(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CLEAR, ResourceType.DRAWABLE),
+
+ // The icon for "done" action. Can be "@null" for no icon.
+ CONFIG_FOOTER_BUTTON_ICON_DONE(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_DONE, ResourceType.DRAWABLE),
+
// The icon for "next" action. Can be "@null" for no icon.
CONFIG_FOOTER_BUTTON_ICON_NEXT(
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_NEXT, ResourceType.DRAWABLE),
+ // The icon for "opt-in" action. Can be "@null" for no icon.
+ CONFIG_FOOTER_BUTTON_ICON_OPT_IN(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_OPT_IN, ResourceType.DRAWABLE),
+
// The icon for "skip" action. Can be "@null" for no icon.
CONFIG_FOOTER_BUTTON_ICON_SKIP(
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_SKIP, ResourceType.DRAWABLE),
- // The icon for "cancel" action. Can be "@null" for no icon.
- CONFIG_FOOTER_BUTTON_ICON_CANCEL(
- PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, ResourceType.DRAWABLE),
-
// The icon for "stop" action. Can be "@null" for no icon.
CONFIG_FOOTER_BUTTON_ICON_STOP(
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_STOP, ResourceType.DRAWABLE),
diff --git a/main/java/com/google/android/setupcompat/util/PartnerConfigKey.java b/main/java/com/google/android/setupcompat/util/PartnerConfigKey.java
index d9bdbb2..1c66940 100644
--- a/main/java/com/google/android/setupcompat/util/PartnerConfigKey.java
+++ b/main/java/com/google/android/setupcompat/util/PartnerConfigKey.java
@@ -28,8 +28,14 @@ import java.lang.annotation.RetentionPolicy;
PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR,
PartnerConfigKey.KEY_WINDOW_LIGHT_NAVIGATION_BAR,
PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CLEAR,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_DONE,
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_NEXT,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_OPT_IN,
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_SKIP,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_STOP,
PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_TOP,
PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_BOTTOM,
PartnerConfigKey.KEY_FOOTER_BUTTON_RADIUS,
@@ -40,6 +46,7 @@ import java.lang.annotation.RetentionPolicy;
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_SIZE,
})
+// TODO(121371322): can be removed and always reference PartnerConfig.getResourceName()?
public @interface PartnerConfigKey {
// Status bar background color or illustration.
String KEY_STATUS_BAR_BACKGROUND = "setup_compat_status_bar_background";
@@ -59,16 +66,28 @@ public @interface PartnerConfigKey {
// available in the system. Font references (@font or @xml) are not allowed.
String KEY_FOOTER_BUTTON_FONT_FAMILY = "setup_compat_footer_button_font_family";
+ // The icon for "add another" action. Can be "@null" for no icon.
+ String KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER = "setup_compat_footer_button_icon_add_another";
+
+ // The icon for "cancel" action. Can be "@null" for no icon.
+ String KEY_FOOTER_BUTTON_ICON_CANCEL = "setup_compat_footer_button_icon_cancel";
+
+ // The icon for "clear" action. Can be "@null" for no icon.
+ String KEY_FOOTER_BUTTON_ICON_CLEAR = "setup_compat_footer_button_icon_clear";
+
+ // The icon for "done" action. Can be "@null" for no icon.
+ String KEY_FOOTER_BUTTON_ICON_DONE = "setup_compat_footer_button_icon_done";
+
// The icon for "next" action. Can be "@null" for no icon.
String KEY_FOOTER_BUTTON_ICON_NEXT = "setup_compat_footer_button_icon_next";
- // The icon for "skip" action. Can be "@null" for no icon.
- String KEY_FOOTER_BUTTON_ICON_SKIP = "setup_compat_footer_button_icon_skip";
+ // The icon for "opt-in" action. Can be "@null" for no icon.
+ String KEY_FOOTER_BUTTON_ICON_OPT_IN = "setup_compat_footer_button_icon_opt_in";
// The icon for "skip" action. Can be "@null" for no icon.
- String KEY_FOOTER_BUTTON_ICON_CANCEL = "setup_compat_footer_button_icon_cancel";
+ String KEY_FOOTER_BUTTON_ICON_SKIP = "setup_compat_footer_button_icon_skip";
- // The icon for "skip" action. Can be "@null" for no icon.
+ // The icon for "stop" action. Can be "@null" for no icon.
String KEY_FOOTER_BUTTON_ICON_STOP = "setup_compat_footer_button_icon_stop";
// Top padding of the footer buttons
diff --git a/main/res/layout/partner_customization_layout.xml b/main/res/layout/partner_customization_layout.xml
index dee2114..32d5356 100644
--- a/main/res/layout/partner_customization_layout.xml
+++ b/main/res/layout/partner_customization_layout.xml
@@ -26,6 +26,18 @@
android:layout_height="match_parent"
android:orientation="vertical">
+ <ImageView
+ android:id="@+id/suc_layout_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@null"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/suc_layout_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
<FrameLayout
android:id="@+id/suc_layout_content"
android:layout_width="match_parent"
diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml
index 16101fa..4b94856 100644
--- a/main/res/values/attrs.xml
+++ b/main/res/values/attrs.xml
@@ -59,11 +59,15 @@
<attr name="android:text" />
<attr name="android:theme" />
<attr name="sucButtonType">
- <enum name="none" value="0" />
- <enum name="next" value="1" />
- <enum name="skip" value="2" />
- <enum name="cancel" value="3" />
- <enum name="stop" value="4" />
+ <enum name="other" value="0" />
+ <enum name="add_another" value="1" />
+ <enum name="cancel" value="2" />
+ <enum name="clear" value="3" />
+ <enum name="done" value="4" />
+ <enum name="next" value="5" />
+ <enum name="opt_in" value="6" />
+ <enum name="skip" value="7" />
+ <enum name="stop" value="8" />
</attr>
</declare-styleable>
@@ -79,5 +83,19 @@
<attr name="sucFooterBarButtonHighlightAlpha" format="float" />
<attr name="sucFooterBarButtonColorControlHighlight" format="color" />
<attr name="sucFooterBarButtonColorControlHighlightRipple" format="color" />
+ <attr name="sucFooterButtonPaddingVertical" format="dimension" />
</declare-styleable>
+
+ <declare-styleable name="SucIconMixin">
+ <attr name="android:icon" />
+ </declare-styleable>
+
+ <declare-styleable name="SucHeaderMixin">
+ <attr name="sucHeaderText" format="string" localization="suggested" />
+ </declare-styleable>
+
+ <declare-styleable name="SucColoredHeaderMixin">
+ <attr name="sucHeaderColor" format="reference|color" />
+ </declare-styleable>
+
</resources>
diff --git a/main/res/values/dimens.xml b/main/res/values/dimens.xml
index 9ba8907..bb860de 100644
--- a/main/res/values/dimens.xml
+++ b/main/res/values/dimens.xml
@@ -17,9 +17,8 @@
<resources>
- <!-- TODO(b/121025996): Remove default values from setup compat, use from theme -->
+ <!-- TODO(b/121025996): Remove default values from setup compat, use from theme -->
<!-- Footer button bar style padding attributes-->
- <dimen name="suc_customization_footer_padding_vertical">8dp</dimen>
<dimen name="suc_customization_footer_min_height">72dp</dimen>
<dimen name="suc_customization_button_margin_start">8dp</dimen>
<dimen name="suc_customization_button_margin_end">20dp</dimen>
diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml
index 41fbf23..52ae37f 100644
--- a/main/res/values/styles.xml
+++ b/main/res/values/styles.xml
@@ -29,16 +29,12 @@
<item name="android:gravity">center_vertical</item>
<item name="android:minHeight">@dimen/suc_customization_footer_min_height</item>
<item name="android:orientation">horizontal</item>
- <item name="android:paddingTop">?attr/sucFooterBarPaddingTop</item>
- <item name="android:paddingBottom">?attr/sucFooterBarPaddingBottom</item>
+ <item name="android:paddingTop">?attr/sucFooterButtonPaddingVertical</item>
+ <item name="android:paddingBottom">?attr/sucFooterButtonPaddingVertical</item>
<item name="android:paddingEnd" tools:ignore="NewApi">@dimen/suc_customization_button_margin_end</item>
<item name="android:paddingLeft">@dimen/suc_customization_button_margin_start</item>
<item name="android:paddingRight">@dimen/suc_customization_button_margin_end</item>
<item name="android:paddingStart" tools:ignore="NewApi">@dimen/suc_customization_button_margin_start</item>
-
- <!-- Default value -->
- <item name="sucFooterBarPaddingTop">@dimen/suc_customization_footer_padding_vertical</item>
- <item name="sucFooterBarPaddingBottom">@dimen/suc_customization_footer_padding_vertical</item>
</style>
<style name="SucPartnerCustomizationButton.Primary" parent="android:Widget.Material.Button.Colored">