summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2022-08-15 22:02:55 -0700
committerXin Li <delphij@google.com>2022-08-15 22:02:55 -0700
commit53fb35126609678aa4a8df95d046b0fe07196322 (patch)
treee94f2f0f46cd6d06fc62643eaf5c64bbb8a7cea7
parentd49534499bbdcebabaccc3a05dc709cc6025d80b (diff)
parent8b591a2253bc542bd13ce6ea47aefd27d8a046dd (diff)
downloadsetupcompat-53fb35126609678aa4a8df95d046b0fe07196322.tar.gz
DO NOT MERGE - Merge Android 13main-16k
Bug: 242648940 Merged-In: I84ad65b397ac38f30f2a4a8ad8ba384fbb50abdb Change-Id: I4d09c5d6c64a3030e70ce591f1ab10f83bba0f02
-rw-r--r--Android.bp1
-rw-r--r--exempting_lint_checks.txt10
-rw-r--r--main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl2
-rw-r--r--main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java37
-rw-r--r--main/java/com/google/android/setupcompat/internal/ExecutorProvider.java6
-rw-r--r--main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java88
-rw-r--r--main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java38
-rw-r--r--main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java15
-rw-r--r--main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java60
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterBarMixin.java43
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java3
-rw-r--r--main/java/com/google/android/setupcompat/util/BuildCompatUtils.java14
-rw-r--r--main/java/com/google/android/setupcompat/util/WizardManagerHelper.java11
-rw-r--r--main/res/values/styles.xml6
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java4
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java91
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java9
-rw-r--r--setup_extension/info_modules.proto96
18 files changed, 412 insertions, 122 deletions
diff --git a/Android.bp b/Android.bp
index 01e654d..d3a42a7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -80,6 +80,7 @@ android_library {
],
static_libs: [
"androidx.annotation_annotation",
+ "error_prone_annotations",
],
min_sdk_version: "14",
sdk_version: "current",
diff --git a/exempting_lint_checks.txt b/exempting_lint_checks.txt
index 90b8caf..4129b20 100644
--- a/exempting_lint_checks.txt
+++ b/exempting_lint_checks.txt
@@ -5,3 +5,13 @@ third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setup
third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/util/SystemBarHelper.java: AnnotateVersionCheck: public static void setImeInsetView(final View view) {
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java: NewApi: bundle.putParcelable(MetricBundleKeys.CUSTOM_EVENT_BUNDLE, CustomEvent.toBundle(customEvent));
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterBarMixin.java: NewApi: onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java: NewApi: FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java: NewApi: FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig(
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/util/SystemBarHelper.java: AnnotateVersionCheck: public static void setImeInsetView(final View view) {
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/view/StatusBarBackgroundLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
diff --git a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
index 09f7c9a..e8cb7e5 100644
--- a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
+++ b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
@@ -26,4 +26,6 @@ interface ISetupCompatService {
oneway void validateActivity(String screenName, in Bundle arguments) = 0;
oneway void logMetric(int metricType, in Bundle arguments, in Bundle extras) = 1;
+
+ oneway void onFocusStatusChanged(in Bundle bundle) = 2;
}
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index e5ba0c5..37cc358 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -29,9 +29,13 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper;
import com.google.android.setupcompat.internal.LifecycleFragment;
import com.google.android.setupcompat.internal.PersistableBundles;
+import com.google.android.setupcompat.internal.SetupCompatServiceInvoker;
import com.google.android.setupcompat.internal.TemplateLayout;
import com.google.android.setupcompat.logging.CustomEvent;
import com.google.android.setupcompat.logging.MetricKey;
@@ -44,6 +48,7 @@ import com.google.android.setupcompat.template.SystemNavBarMixin;
import com.google.android.setupcompat.util.BuildCompatUtils;
import com.google.android.setupcompat.util.Logger;
import com.google.android.setupcompat.util.WizardManagerHelper;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** A templatization layout with consistent style used in Setup Wizard or app itself. */
public class PartnerCustomizationLayout extends TemplateLayout {
@@ -71,24 +76,33 @@ public class PartnerCustomizationLayout extends TemplateLayout {
private Activity activity;
+ @CanIgnoreReturnValue
public PartnerCustomizationLayout(Context context) {
this(context, 0, 0);
}
+ @CanIgnoreReturnValue
public PartnerCustomizationLayout(Context context, int template) {
this(context, template, 0);
}
+ @CanIgnoreReturnValue
public PartnerCustomizationLayout(Context context, int template, int containerId) {
super(context, template, containerId);
init(null, R.attr.sucLayoutTheme);
}
+ @VisibleForTesting
+ final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener =
+ this::onFocusChanged;
+
+ @CanIgnoreReturnValue
public PartnerCustomizationLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, R.attr.sucLayoutTheme);
}
+ @CanIgnoreReturnValue
@TargetApi(VERSION_CODES.HONEYCOMB)
public PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -203,14 +217,17 @@ public class PartnerCustomizationLayout extends TemplateLayout {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
LifecycleFragment.attachNow(activity);
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2
+ && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
+ getViewTreeObserver().addOnWindowFocusChangeListener(windowFocusChangeListener);
+ }
getMixin(FooterBarMixin.class).onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+ if (VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class);
footerBarMixin.onDetachedFromWindow();
@@ -233,6 +250,10 @@ public class PartnerCustomizationLayout extends TemplateLayout {
getContext(),
CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle));
}
+
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
+ getViewTreeObserver().removeOnWindowFocusChangeListener(windowFocusChangeListener);
+ }
}
public static Activity lookupActivityFromContext(Context context) {
@@ -297,4 +318,16 @@ public class PartnerCustomizationLayout extends TemplateLayout {
public boolean useFullDynamicColor() {
return shouldApplyDynamicColor() && useFullDynamicColorAttr;
}
+
+ /**
+ * Invoke the method onFocusStatusChanged when onWindowFocusChangeListener receive onFocusChanged.
+ */
+ private void onFocusChanged(boolean hasFocus) {
+ SetupCompatServiceInvoker.get(getContext())
+ .onFocusStatusChanged(
+ FocusChangedMetricHelper.getScreenName(activity),
+ FocusChangedMetricHelper.getExtraBundle(
+ activity, PartnerCustomizationLayout.this, hasFocus));
+ }
}
+
diff --git a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
index 3c707ae..28ced66 100644
--- a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
@@ -33,7 +33,6 @@ import java.util.concurrent.TimeUnit;
public final class ExecutorProvider<T extends Executor> {
private static final int SETUP_METRICS_LOGGER_MAX_QUEUED = 50;
- private static final int SETUP_COMPAT_BINDBACK_MAX_QUEUED = 1;
/**
* Creates a single threaded {@link ExecutorService} with a maximum pool size {@code maxSize}.
* Jobs submitted when the pool is full causes {@link
@@ -43,11 +42,6 @@ public final class ExecutorProvider<T extends Executor> {
new ExecutorProvider<>(
createSizeBoundedExecutor("SetupCompatServiceInvoker", SETUP_METRICS_LOGGER_MAX_QUEUED));
- public static final ExecutorProvider<ExecutorService> setupCompatExecutor =
- new ExecutorProvider<>(
- createSizeBoundedExecutor(
- "SetupBindbackServiceExecutor", SETUP_COMPAT_BINDBACK_MAX_QUEUED));
-
private final T executor;
@Nullable private T injectedExecutor;
diff --git a/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
new file mode 100644
index 0000000..39ade19
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 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 android.app.Activity;
+import android.os.Bundle;
+import androidx.annotation.StringDef;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper.Constants.ExtraKey;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A help class response to generate extra bundle and capture screen name for interruption metric.
+ */
+public class FocusChangedMetricHelper {
+ private FocusChangedMetricHelper() {}
+
+ public static final String getScreenName(Activity activity) {
+ return activity.getComponentName().toShortString();
+ }
+
+ public static final Bundle getExtraBundle(
+ Activity activity, TemplateLayout layout, boolean hasFocus) {
+ Bundle bundle = new Bundle();
+
+ bundle.putString(ExtraKey.PACKAGE_NAME, activity.getComponentName().getPackageName());
+ bundle.putString(ExtraKey.SCREEN_NAME, activity.getComponentName().getShortClassName());
+ bundle.putInt(ExtraKey.HASH_CODE, layout.hashCode());
+ bundle.putBoolean(ExtraKey.HAS_FOCUS, hasFocus);
+ bundle.putLong(ExtraKey.TIME_IN_MILLIS, System.currentTimeMillis());
+
+ return bundle;
+ }
+
+ /**
+ * Constant values used by {@link
+ * com.google.android.setupcompat.internal.FocusChangedMetricHelper}.
+ */
+ public static final class Constants {
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ ExtraKey.PACKAGE_NAME,
+ ExtraKey.SCREEN_NAME,
+ ExtraKey.HASH_CODE,
+ ExtraKey.HAS_FOCUS,
+ ExtraKey.TIME_IN_MILLIS
+ })
+ public @interface ExtraKey {
+
+ /** This key will be used to save the package name. */
+ String PACKAGE_NAME = "packageName";
+
+ /** This key will be used to save the activity name. */
+ String SCREEN_NAME = "screenName";
+
+ /**
+ * This key will be used to save the has code of {@link
+ * com.google.android.setupcompat.PartnerCustomizationLayout}.
+ */
+ String HASH_CODE = "hash";
+
+ /**
+ * This key will be used to save whether the window which is including the {@link
+ * com.google.android.setupcompat.PartnerCustomizationLayout}. has focus or not.
+ */
+ String HAS_FOCUS = "focus";
+
+ /** This key will be use to save the time stamp in milliseconds. */
+ String TIME_IN_MILLIS = "timeInMillis";
+ }
+
+ private Constants() {}
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index 149da54..ed9c0e3 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -54,12 +54,23 @@ public class SetupCompatServiceInvoker {
public void bindBack(String screenName, Bundle bundle) {
try {
- setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
+ loggingExecutor.execute(() -> invokeBindBack(screenName, bundle));
} catch (RejectedExecutionException e) {
LOG.e(String.format("Screen %s bind back fail.", screenName), e);
}
}
+ /**
+ * Help invoke the {@link ISetupCompatService#onFocusStatusChanged} using {@code loggingExecutor}.
+ */
+ public void onFocusStatusChanged(String screenName, Bundle bundle) {
+ try {
+ loggingExecutor.execute(() -> invokeOnWindowFocusChanged(screenName, bundle));
+ } catch (RejectedExecutionException e) {
+ LOG.e(String.format("Screen %s report focus changed failed.", screenName), e);
+ }
+ }
+
private void invokeLogMetric(
@MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
try {
@@ -76,6 +87,29 @@ public class SetupCompatServiceInvoker {
}
}
+ private void invokeOnWindowFocusChanged(String screenName, Bundle bundle) {
+ try {
+ ISetupCompatService setupCompatService =
+ SetupCompatServiceProvider.get(
+ context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
+ if (setupCompatService != null) {
+ setupCompatService.onFocusStatusChanged(bundle);
+ } else {
+ LOG.w(
+ "Report focusChange failed since service reference is null. Are the permission valid?");
+ }
+ } catch (InterruptedException
+ | TimeoutException
+ | RemoteException
+ | UnsupportedOperationException e) {
+ LOG.e(
+ String.format(
+ "Exception occurred while %s trying report windowFocusChange to SetupWizard.",
+ screenName),
+ e);
+ }
+ }
+
private void invokeBindBack(String screenName, Bundle bundle) {
try {
ISetupCompatService setupCompatService =
@@ -96,14 +130,12 @@ public class SetupCompatServiceInvoker {
private SetupCompatServiceInvoker(Context context) {
this.context = context;
this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
- this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get();
this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
}
private final Context context;
private final ExecutorService loggingExecutor;
- private final ExecutorService setupCompatExecutor;
private final long waitTimeInMillisForServiceConnection;
public static synchronized SetupCompatServiceInvoker get(Context context) {
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
index e75d991..e259f22 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
@@ -82,8 +82,8 @@ public class SetupCompatServiceProvider {
return waitForConnection(timeout, timeUnit);
case NOT_STARTED:
- throw new IllegalStateException(
- "NOT_STARTED state only possible before instance is created.");
+ LOG.w("NOT_STARTED state only possible before instance is created.");
+ return null;
}
throw new IllegalStateException("Unknown state = " + serviceContext.state);
}
@@ -172,7 +172,8 @@ public class SetupCompatServiceProvider {
return serviceContext;
}
- private void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
+ @VisibleForTesting
+ void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
LOG.atInfo(
String.format("State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
@@ -200,7 +201,7 @@ public class SetupCompatServiceProvider {
return countDownLatch;
}
countDownLatch = createCountDownLatch();
- } while (!connectedConditionRef.compareAndSet(/* expect= */ null, countDownLatch));
+ } while (!connectedConditionRef.compareAndSet(/* expectedValue= */ null, countDownLatch));
return countDownLatch;
}
@@ -285,7 +286,8 @@ public class SetupCompatServiceProvider {
REBIND_REQUIRED
}
- private static final class ServiceContext {
+ @VisibleForTesting
+ static final class ServiceContext {
final State state;
@Nullable final ISetupCompatService compatService;
@@ -298,7 +300,8 @@ public class SetupCompatServiceProvider {
}
}
- private ServiceContext(State state) {
+ @VisibleForTesting
+ ServiceContext(State state) {
this(state, /* compatService= */ null);
}
}
diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
deleted file mode 100644
index 2aa1240..0000000
--- a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.logging.internal;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build.VERSION_CODES;
-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. */
-@TargetApi(VERSION_CODES.Q)
-public class PartnerCustomizedResourceListMetric {
-
- public static void logMetrics(Context context, String deviceDisplayName, Bundle bundle) {
- PersistableBundle logBundle =
- buildLogBundleFromResourceConfigBundle(context.getPackageName(), deviceDisplayName, bundle);
- if (!logBundle.isEmpty()) {
- SetupMetricsLogger.logCustomEvent(
- context,
- CustomEvent.create(
- MetricKey.get("PartnerCustomizationResource", "NoScreenName"), logBundle));
- }
- }
-
- @VisibleForTesting
- public static PersistableBundle buildLogBundleFromResourceConfigBundle(
- String defaultPackageName, String deviceDisplayName, Bundle resourceConfigBundle) {
- PersistableBundle persistableBundle = new PersistableBundle();
- persistableBundle.putString("deviceDisplayName", deviceDisplayName);
- for (String key : resourceConfigBundle.keySet()) {
- Bundle resourceExtra = resourceConfigBundle.getBundle(key);
- if (!resourceExtra.getString("packageName", defaultPackageName).equals(defaultPackageName)) {
- persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), true);
- } else {
- persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), false);
- }
- }
-
- return persistableBundle;
- }
-}
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index 6d92c40..4d2a0c9 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -17,6 +17,7 @@
package com.google.android.setupcompat.template;
import static com.google.android.setupcompat.internal.Preconditions.ensureOnMainThread;
+import static java.lang.Math.max;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -73,7 +74,7 @@ public class FooterBarMixin implements Mixin {
@VisibleForTesting final boolean applyDynamicColor;
@VisibleForTesting final boolean useFullDynamicColor;
- @VisibleForTesting LinearLayout buttonContainer;
+ @VisibleForTesting public LinearLayout buttonContainer;
private FooterButton primaryButton;
private FooterButton secondaryButton;
@IdRes private int primaryButtonId;
@@ -226,7 +227,7 @@ public class FooterBarMixin implements Mixin {
FooterButtonStyleUtils.clearSavedDefaultTextColor();
}
- private boolean isFooterButtonAlignedEnd() {
+ protected boolean isFooterButtonAlignedEnd() {
if (PartnerConfigHelper.get(context)
.isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END)) {
return PartnerConfigHelper.get(context)
@@ -240,18 +241,16 @@ public class FooterBarMixin implements Mixin {
if (!isSecondaryButtonInPrimaryStyle) {
return false;
}
- // TODO: Support neutral button style in glif layout for phone and tablet
PartnerConfigHelper.get(context);
- return context.getResources().getConfiguration().smallestScreenWidthDp >= 600
- && PartnerConfigHelper.isNeutralButtonStyleEnabled(context);
+ return PartnerConfigHelper.isNeutralButtonStyleEnabled(context);
}
private View addSpace() {
- LinearLayout buttonContainer = ensureFooterInflated();
+ LinearLayout buttonContainerlayout = ensureFooterInflated();
View space = new View(context);
space.setLayoutParams(new LayoutParams(0, 0, 1.0f));
space.setVisibility(View.INVISIBLE);
- buttonContainer.addView(space);
+ buttonContainerlayout.addView(space);
return space;
}
@@ -531,8 +530,7 @@ public class FooterBarMixin implements Mixin {
}
buttonContainer.addView(tempSecondaryButton);
}
- if (!isFooterButtonAlignedEnd()
- && (!isEvenlyWeightedButtons || (isEvenlyWeightedButtons && isLandscape))) {
+ if (!isFooterButtonAlignedEnd()) {
addSpace();
}
if (tempPrimaryButton != null) {
@@ -545,21 +543,15 @@ public class FooterBarMixin implements Mixin {
private void setEvenlyWeightedButtons(
Button primaryButton, Button secondaryButton, boolean isEvenlyWeighted) {
if (primaryButton != null && secondaryButton != null && isEvenlyWeighted) {
- LinearLayout.LayoutParams primaryLayoutParams =
- (LinearLayout.LayoutParams) primaryButton.getLayoutParams();
- if (null != primaryLayoutParams) {
- primaryLayoutParams.width = 0;
- primaryLayoutParams.weight = 1.0f;
- primaryButton.setLayoutParams(primaryLayoutParams);
- }
+ primaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ int primaryButtonMeasuredWidth = primaryButton.getMeasuredWidth();
+ secondaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- LinearLayout.LayoutParams secondaryLayoutParams =
- (LinearLayout.LayoutParams) secondaryButton.getLayoutParams();
- if (null != secondaryLayoutParams) {
- secondaryLayoutParams.width = 0;
- secondaryLayoutParams.weight = 1.0f;
- secondaryButton.setLayoutParams(secondaryLayoutParams);
- }
+ int secondaryButtonMeasuredWidth = secondaryButton.getMeasuredWidth();
+ int maxButtonMeasureWidth = max(primaryButtonMeasuredWidth, secondaryButtonMeasuredWidth);
+
+ primaryButton.getLayoutParams().width = maxButtonMeasureWidth;
+ secondaryButton.getLayoutParams().width = maxButtonMeasureWidth;
} else {
if (primaryButton != null) {
LinearLayout.LayoutParams primaryLayoutParams =
@@ -615,10 +607,7 @@ public class FooterBarMixin implements Mixin {
int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig);
if (color == Color.TRANSPARENT) {
overrideTheme = R.style.SucPartnerCustomizationButton_Secondary;
- } else if (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.
+ } else {
overrideTheme = R.style.SucPartnerCustomizationButton_Primary;
}
}
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
index ef2aa6b..476d45b 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -35,7 +35,6 @@ import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.ColorInt;
-import androidx.annotation.VisibleForTesting;
import com.google.android.setupcompat.R;
import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
import com.google.android.setupcompat.internal.Preconditions;
@@ -429,7 +428,7 @@ public class FooterButtonStyleUtils {
defaultTextColor.clear();
}
- @VisibleForTesting
+ /** Gets {@code GradientDrawable} from given {@code button}. */
public static GradientDrawable getGradientDrawable(Button button) {
// RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
// sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
index 5b7c3ad..3c4e2a2 100644
--- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
+++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
@@ -60,26 +60,26 @@ public final class BuildCompatUtils {
* <p>Supported configurations:
*
* <ul>
- * <li>For current Android release: while new API is not finalized yet (CODENAME = "T", SDK_INT
- * = 33)
+ * <li>For current Android release: while new API is not finalized yet (CODENAME = "Tiramisu",
+ * SDK_INT = 33)
* <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 32)
* <li>For next Android release (CODENAME = "U", SDK_INT = 34+)
* </ul>
*
* <p>Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all
- * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API
- * finalization.
+ * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API finalization.
*
* @return Whether the current OS version is higher or equal to T.
*/
public static boolean isAtLeastT() {
- if (!isAtLeastS()) {
- return false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return true;
}
return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33)
|| (Build.VERSION.CODENAME.length() == 1
&& Build.VERSION.CODENAME.charAt(0) >= 'T'
- && Build.VERSION.CODENAME.charAt(0) <= 'Z');
+ && Build.VERSION.CODENAME.charAt(0) <= 'Z')
+ || (Build.VERSION.CODENAME.equals("Tiramisu") && Build.VERSION.SDK_INT >= 32);
}
private BuildCompatUtils() {}
diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
index 84bd68b..90de25e 100644
--- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
+++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
@@ -186,6 +186,17 @@ public final class WizardManagerHelper {
}
/**
+ * Checks whether an intent is running in the portal setup wizard flow.
+ *
+ * @param originalIntent The original intent that was used to start the step, usually via {@link
+ * Activity#getIntent()}.
+ * @return true if the intent passed in was running in portal setup wizard.
+ */
+ public static boolean isPortalSetupWizard(Intent originalIntent) {
+ return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_PORTAL_SETUP, false);
+ }
+
+ /**
* Checks whether an intent is running in the deferred setup wizard flow.
*
* @param originalIntent The original intent that was used to start the step, usually via {@link
diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml
index 6474426..6625e83 100644
--- a/main/res/values/styles.xml
+++ b/main/res/values/styles.xml
@@ -45,13 +45,13 @@
<item name="android:theme">@style/SucPartnerCustomizationButton.Primary</item>
<!-- Values used in styles -->
- <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
+ <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</item>
<item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
<item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
<item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item>
<item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item>
<item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item>
- <item name="android:stateListAnimator">@null</item>
+ <item name="android:stateListAnimator" tools:ignore="NewApi">@null</item>
<!-- Values used in themes -->
<item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/sucFooterBarButtonCornerRadius</item>
@@ -65,7 +65,7 @@
<item name="android:theme">@style/SucPartnerCustomizationButton.Secondary</item>
<!-- Values used in styles -->
- <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
+ <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</item>
<item name="android:minWidth">0dp</item>
<item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
<item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 442e86c..c9a1966 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -456,6 +456,10 @@ public enum PartnerConfig {
CONFIG_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED(
PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, ResourceType.BOOL),
+ // Waiting for the animation finished before process to the next page/action.
+ CONFIG_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED, ResourceType.BOOL),
+
// The margin top of progress bar.
CONFIG_PROGRESS_BAR_MARGIN_TOP(
PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, ResourceType.DIMENSION),
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index 3525fa1..aca9a07 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -35,6 +35,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType;
+import com.google.android.setupcompat.util.BuildCompatUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
@@ -60,15 +61,23 @@ public class PartnerConfigHelper {
"isExtendedPartnerConfigEnabled";
@VisibleForTesting
+ public static final String IS_MATERIAL_YOU_STYLE_ENABLED_METHOD = "IsMaterialYouStyleEnabled";
+
+ @VisibleForTesting
public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled";
@VisibleForTesting
public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled";
+ @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard";
+ @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you";
+
@VisibleForTesting static Bundle suwDayNightEnabledBundle = null;
@VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null;
+ @VisibleForTesting public static Bundle applyMaterialYouConfigBundle = null;
+
@VisibleForTesting public static Bundle applyDynamicColorBundle = null;
@VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null;
@@ -84,7 +93,8 @@ public class PartnerConfigHelper {
private static int savedConfigUiMode;
- private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
+ @VisibleForTesting
+ public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
/**
* When testing related to fake PartnerConfigHelper instance, should sync the following saved
@@ -538,9 +548,10 @@ public class PartnerConfigHelper {
if (fallbackBundle != null) {
resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName));
}
-
- return adjustResourceEntryDayNightMode(
- context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+ ResourceEntry adjustResourceEntry =
+ adjustResourceEntryDefaultValue(
+ context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+ return adjustResourceEntryDayNightMode(context, adjustResourceEntry);
}
/**
@@ -565,11 +576,50 @@ public class PartnerConfigHelper {
return resourceEntry;
}
+ // Check the MNStyle flag and replace the inputResourceEntry.resourceName &
+ // inputResourceEntry.resourceId after T, that means if using Gliv4 before S, will always use
+ // glifv3 resources.
+ ResourceEntry adjustResourceEntryDefaultValue(Context context, ResourceEntry inputResourceEntry) {
+ if (BuildCompatUtils.isAtLeastT() && shouldApplyMaterialYouStyle(context)) {
+ // If not overlay resource
+ try {
+ if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) {
+ String resourceTypeName =
+ inputResourceEntry
+ .getResources()
+ .getResourceTypeName(inputResourceEntry.getResourceId());
+ // try to update resourceName & resourceId
+ String materialYouResourceName =
+ inputResourceEntry.getResourceName().concat(MATERIAL_YOU_RESOURCE_SUFFIX);
+ int materialYouResourceId =
+ inputResourceEntry
+ .getResources()
+ .getIdentifier(
+ materialYouResourceName,
+ resourceTypeName,
+ inputResourceEntry.getPackageName());
+ if (materialYouResourceId != 0) {
+ Log.i(TAG, "use material you resource:" + materialYouResourceName);
+ return new ResourceEntry(
+ inputResourceEntry.getPackageName(),
+ materialYouResourceName,
+ materialYouResourceId,
+ inputResourceEntry.getResources());
+ }
+ }
+ } catch (NotFoundException ex) {
+ // fall through
+ }
+ }
+ return inputResourceEntry;
+ }
+
@VisibleForTesting
public static synchronized void resetInstance() {
instance = null;
suwDayNightEnabledBundle = null;
applyExtendedPartnerConfigBundle = null;
+ applyMaterialYouConfigBundle = null;
applyDynamicColorBundle = null;
applyNeutralButtonStyleBundle = null;
}
@@ -628,6 +678,39 @@ public class PartnerConfigHelper {
IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false));
}
+ /**
+ * Returns true if the SetupWizard is flow enabled "Material You(Glifv4)" style, or the result of
+ * shouldApplyExtendedPartnerConfig() in SDK S as fallback.
+ */
+ public static boolean shouldApplyMaterialYouStyle(@NonNull Context context) {
+ if (applyMaterialYouConfigBundle == null || applyMaterialYouConfigBundle.isEmpty()) {
+ try {
+ applyMaterialYouConfigBundle =
+ context
+ .getContentResolver()
+ .call(
+ getContentUri(),
+ IS_MATERIAL_YOU_STYLE_ENABLED_METHOD,
+ /* arg= */ null,
+ /* extras= */ null);
+ // The suw version did not support the flag yet, fallback to
+ // shouldApplyExtendedPartnerConfig() for SDK S.
+ if (applyMaterialYouConfigBundle != null
+ && applyMaterialYouConfigBundle.isEmpty()
+ && !BuildCompatUtils.isAtLeastT()) {
+ return shouldApplyExtendedPartnerConfig(context);
+ }
+ } catch (IllegalArgumentException | SecurityException exception) {
+ Log.w(TAG, "SetupWizard Material You configs supporting status unknown; return as false.");
+ applyMaterialYouConfigBundle = null;
+ return false;
+ }
+ }
+
+ return (applyMaterialYouConfigBundle != null
+ && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, false));
+ }
+
/** Returns true if the SetupWizard supports the dynamic color during setup flow. */
public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
if (applyDynamicColorBundle == null) {
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index cbf72f5..9554ff3 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -138,6 +138,7 @@ import java.lang.annotation.RetentionPolicy;
PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM,
PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT,
PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED,
PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP,
PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM,
})
@@ -403,10 +404,10 @@ public @interface PartnerConfigKey {
// The divider of list items are showing.
String KEY_ITEMS_DIVIDER_SHOWN = "setup_design_items_divider_shown";
- // The intrinsic width of the card view for foldabe/tablet.
+ // The intrinsic width of the card view for foldable/tablet.
String KEY_CARD_VIEW_INTRINSIC_WIDTH = "setup_design_card_view_intrinsic_width";
- // The intrinsic height of the card view for foldabe/tablet.
+ // The intrinsic height of the card view for foldable/tablet.
String KEY_CARD_VIEW_INTRINSIC_HEIGHT = "setup_design_card_view_intrinsic_height";
// The animation of loading screen used in those activities which is non of below type.
@@ -520,6 +521,10 @@ public @interface PartnerConfigKey {
String KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED =
"loading_layout_full_screen_illustration_enabled";
+ // Waiting the animation finished before process to the next page/action.
+ String KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED =
+ "loading_layout_wait_for_animation_finished";
+
// A margin top of the content frame of progress bar.
String KEY_PROGRESS_BAR_MARGIN_TOP = "setup_design_progress_bar_margin_top";
diff --git a/setup_extension/info_modules.proto b/setup_extension/info_modules.proto
new file mode 100644
index 0000000..5027c4c
--- /dev/null
+++ b/setup_extension/info_modules.proto
@@ -0,0 +1,96 @@
+// Copyright (C) 2022 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.
+//
+// Author: yuchaoyu@google.com (Yu Chao)
+
+syntax = "proto2";
+
+package portal_tips_proto;
+
+option java_package = "com.google.android.setupcompat";
+option java_outer_classname = "InfoModules";
+
+// A list of informational modules to be displayed under the deferred setup
+// items.
+// Next index: 5
+message InfoModuleList {
+ // Contains the title of the entire info module section. This is displayed
+ // before the subtitle and the list of info modules.
+ optional string section_title = 1;
+
+ // Contains the subtitle of the entire info module section. This is displayed
+ // before the list of info modules.
+ optional string section_subtitle = 2;
+
+ // A module represents a single informational item to be displayed in the list
+ // under the deferred setup page.
+ //
+ // Each module contains a unique id, three sections of text labels (title,
+ // description, footer), the Intent URI to launch when being clicked, the raw
+ // icon, and a boolean to determine whether or not the icon should be colored.
+ // Next index: 8
+ message InfoModule {
+
+ // The id to uniquely identify an InfoModule.
+ optional string id = 1;
+
+ // Contains the title of the module. This is displayed as the first section
+ // of the text content.
+ optional string title = 2;
+
+ // Contains the main paragraphs of the module. This is displayed as the
+ // second section of the text content.
+ optional string description = 3;
+
+ // Contains the footer text of the module. This is displayed as the third
+ // and final section of the text content.
+ optional string footer = 4;
+
+ // Contains the Intent string to be launched when the module is clicked.
+ //
+ // This string could be generated from an `Intent` object through the
+ // `toUri()` method.
+ optional string intent_uri = 5;
+
+ // Contains the icon of the module in raw bytes of bitmap format.
+ optional bytes raw_icon = 6;
+
+ // Determines whether the icon should be colored according to the theme.
+ // Generally speaking, this should be set to true for monoline icons (such
+ // as Material Icons) because their colors change according to the theme.
+ // For icons which their colors should be preserved (such as package icons),
+ // this should be set to false.
+ optional bool should_apply_theme_color_on_icon = 7;
+ }
+
+ // Contains a list of informational modules to be displayed.
+ repeated InfoModule info_module_list = 3;
+
+ // A trailing link is a button item appearing below the info module list.
+ // Next index: 3
+ message TrailingLink {
+ // Contains the text that should be displayed on the link button.
+ optional string text = 1;
+
+ // Contains the Intent string to be launched when the link button is
+ // clicked.
+ //
+ // This string could be generated from an `Intent` object through the
+ // `toUri()` method.
+ optional string intent_uri = 2;
+ }
+
+ // Contains a trailing link to be displayed below the info modules.
+ optional TrailingLink trailing_link = 4;
+}