diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:25:56 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:25:56 +0000 |
commit | bf4fa7dbaa316a9dc62e49ab155c2bbac70c42e4 (patch) | |
tree | 9413722f83f317d6819cd7f7d044249461c3770e | |
parent | 33cf4c96e8c4986edf036ec2c51228d5b9905bd2 (diff) | |
parent | 47ba3c2a7b5fdb0cb4df38966b4ae81ab3d06cf8 (diff) | |
download | setupcompat-bf4fa7dbaa316a9dc62e49ab155c2bbac70c42e4.tar.gz |
Snap for 8564071 from 47ba3c2a7b5fdb0cb4df38966b4ae81ab3d06cf8 to mainline-art-release
Change-Id: If8d3ccd09a3567332968524100028f304adee3b3
26 files changed, 1093 insertions, 158 deletions
@@ -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/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java index 5f8bf67..8e23c1a 100644 --- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java +++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java @@ -24,8 +24,10 @@ public class FooterButtonPartnerConfig { private final PartnerConfig buttonBackgroundConfig; private final PartnerConfig buttonDisableAlphaConfig; private final PartnerConfig buttonDisableBackgroundConfig; + private final PartnerConfig buttonDisableTextColorConfig; private final PartnerConfig buttonIconConfig; private final PartnerConfig buttonTextColorConfig; + private final PartnerConfig buttonMarginStartConfig; private final PartnerConfig buttonTextSizeConfig; private final PartnerConfig buttonMinHeightConfig; private final PartnerConfig buttonTextTypeFaceConfig; @@ -39,8 +41,10 @@ public class FooterButtonPartnerConfig { PartnerConfig buttonBackgroundConfig, PartnerConfig buttonDisableAlphaConfig, PartnerConfig buttonDisableBackgroundConfig, + PartnerConfig buttonDisableTextColorConfig, PartnerConfig buttonIconConfig, PartnerConfig buttonTextColorConfig, + PartnerConfig buttonMarginStartConfig, PartnerConfig buttonTextSizeConfig, PartnerConfig buttonMinHeightConfig, PartnerConfig buttonTextTypeFaceConfig, @@ -50,6 +54,7 @@ public class FooterButtonPartnerConfig { this.partnerTheme = partnerTheme; this.buttonTextColorConfig = buttonTextColorConfig; + this.buttonMarginStartConfig = buttonMarginStartConfig; this.buttonTextSizeConfig = buttonTextSizeConfig; this.buttonMinHeightConfig = buttonMinHeightConfig; this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig; @@ -57,6 +62,7 @@ public class FooterButtonPartnerConfig { this.buttonBackgroundConfig = buttonBackgroundConfig; this.buttonDisableAlphaConfig = buttonDisableAlphaConfig; this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig; + this.buttonDisableTextColorConfig = buttonDisableTextColorConfig; this.buttonRadiusConfig = buttonRadiusConfig; this.buttonIconConfig = buttonIconConfig; this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig; @@ -78,6 +84,10 @@ public class FooterButtonPartnerConfig { return buttonDisableBackgroundConfig; } + public PartnerConfig getButtonDisableTextColorConfig() { + return buttonDisableTextColorConfig; + } + public PartnerConfig getButtonIconConfig() { return buttonIconConfig; } @@ -86,6 +96,10 @@ public class FooterButtonPartnerConfig { return buttonTextColorConfig; } + public PartnerConfig getButtonMarginStartConfig() { + return buttonMarginStartConfig; + } + public PartnerConfig getButtonMinHeightConfig() { return buttonMinHeightConfig; } @@ -116,8 +130,10 @@ public class FooterButtonPartnerConfig { private PartnerConfig buttonBackgroundConfig = null; private PartnerConfig buttonDisableAlphaConfig = null; private PartnerConfig buttonDisableBackgroundConfig = null; + private PartnerConfig buttonDisableTextColorConfig = null; private PartnerConfig buttonIconConfig = null; private PartnerConfig buttonTextColorConfig = null; + private PartnerConfig buttonMarginStartConfig = null; private PartnerConfig buttonTextSizeConfig = null; private PartnerConfig buttonMinHeight = null; private PartnerConfig buttonTextTypeFaceConfig = null; @@ -149,11 +165,21 @@ public class FooterButtonPartnerConfig { return this; } + public Builder setButtonDisableTextColorConfig(PartnerConfig buttonDisableTextColorConfig) { + this.buttonDisableTextColorConfig = buttonDisableTextColorConfig; + return this; + } + public Builder setButtonIconConfig(PartnerConfig buttonIconConfig) { this.buttonIconConfig = buttonIconConfig; return this; } + public Builder setMarginStartConfig(PartnerConfig buttonMarginStartConfig) { + this.buttonMarginStartConfig = buttonMarginStartConfig; + return this; + } + public Builder setTextColorConfig(PartnerConfig buttonTextColorConfig) { this.buttonTextColorConfig = buttonTextColorConfig; return this; @@ -200,8 +226,10 @@ public class FooterButtonPartnerConfig { buttonBackgroundConfig, buttonDisableAlphaConfig, buttonDisableBackgroundConfig, + buttonDisableTextColorConfig, buttonIconConfig, buttonTextColorConfig, + buttonMarginStartConfig, buttonTextSizeConfig, buttonMinHeight, buttonTextTypeFaceConfig, 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/portal/NotificationComponent.java b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java index a90963b..c5865fe 100644 --- a/main/java/com/google/android/setupcompat/portal/NotificationComponent.java +++ b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java @@ -76,6 +76,7 @@ public class NotificationComponent implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({ + NotificationType.UNKNOWN, NotificationType.INITIAL_ONGOING, NotificationType.PREDEFERRED, NotificationType.PREDEFERRED_PREPARING, diff --git a/main/java/com/google/android/setupcompat/template/FooterActionButton.java b/main/java/com/google/android/setupcompat/template/FooterActionButton.java index 86a06d9..d9726f9 100644 --- a/main/java/com/google/android/setupcompat/template/FooterActionButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterActionButton.java @@ -28,6 +28,7 @@ import androidx.annotation.Nullable; public class FooterActionButton extends Button { @Nullable private FooterButton footerButton; + private boolean isPrimaryButtonStyle = false; public FooterActionButton(Context context, AttributeSet attrs) { super(context, attrs); @@ -54,4 +55,18 @@ public class FooterActionButton extends Button { } return super.onTouchEvent(event); } + + /** + * Sets this footer button is primary button style. + * + * @param isPrimaryButtonStyle True if this button is primary button style. + */ + void setPrimaryButtonStyle(boolean isPrimaryButtonStyle) { + this.isPrimaryButtonStyle = isPrimaryButtonStyle; + } + + /** Returns true when the footer button is primary button style. */ + public boolean isPrimaryButtonStyle() { + return isPrimaryButtonStyle; + } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index b75d972..4d78955 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -17,11 +17,12 @@ 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; import android.content.Context; -import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; @@ -29,6 +30,7 @@ import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import android.util.AttributeSet; import android.view.ContextThemeWrapper; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewStub; @@ -71,18 +73,18 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; - private LinearLayout buttonContainer; + @VisibleForTesting public LinearLayout buttonContainer; private FooterButton primaryButton; private FooterButton secondaryButton; @IdRes private int primaryButtonId; @IdRes private int secondaryButtonId; - ColorStateList primaryDefaultTextColor = null; - ColorStateList secondaryDefaultTextColor = null; @VisibleForTesting public FooterButtonPartnerConfig primaryButtonPartnerConfigForTesting; @VisibleForTesting public FooterButtonPartnerConfig secondaryButtonPartnerConfigForTesting; private int footerBarPaddingTop; private int footerBarPaddingBottom; + @VisibleForTesting int footerBarPaddingStart; + @VisibleForTesting int footerBarPaddingEnd; @VisibleForTesting int defaultPadding; @ColorInt private final int footerBarPrimaryBackgroundColor; @ColorInt private final int footerBarSecondaryBackgroundColor; @@ -104,11 +106,15 @@ public class FooterBarMixin implements Mixin { if (button != null) { button.setEnabled(enabled); if (applyPartnerResources && !applyDynamicColor) { - updateButtonTextColorWithEnabledState( + + updateButtonTextColorWithStates( button, (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR - : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, + (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR); } } } @@ -189,6 +195,10 @@ public class FooterBarMixin implements Mixin { footerBarPaddingBottom = a.getDimensionPixelSize( R.styleable.SucFooterBarMixin_sucFooterBarPaddingBottom, defaultPadding); + footerBarPaddingStart = + a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingStart, 0); + footerBarPaddingEnd = + a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingEnd, 0); footerBarPrimaryBackgroundColor = a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); footerBarSecondaryBackgroundColor = @@ -212,14 +222,34 @@ public class FooterBarMixin implements Mixin { metrics.logSecondaryButtonInitialStateVisibility( /* isVisible= */ true, /* isUsingXml= */ true); } + + FooterButtonStyleUtils.clearSavedDefaultTextColor(); + } + + protected boolean isFooterButtonAlignedEnd() { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END)) { + return PartnerConfigHelper.get(context) + .getBoolean(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END, false); + } else { + return false; + } + } + + protected boolean isFooterButtonsEvenlyWeighted() { + if (!isSecondaryButtonInPrimaryStyle) { + return false; + } + PartnerConfigHelper.get(context); + return PartnerConfigHelper.isNeutralButtonStyleEnabled(context); } private View addSpace() { - LinearLayout buttonContainer = ensureFooterInflated(); - View space = new View(buttonContainer.getContext()); + 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; } @@ -253,10 +283,13 @@ public class FooterBarMixin implements Mixin { } updateFooterBarPadding( buttonContainer, - buttonContainer.getPaddingLeft(), + footerBarPaddingStart, footerBarPaddingTop, - buttonContainer.getPaddingRight(), + footerBarPaddingEnd, footerBarPaddingBottom); + if (isFooterButtonAlignedEnd()) { + buttonContainer.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); + } } /** @@ -282,19 +315,39 @@ public class FooterBarMixin implements Mixin { 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); + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP)) { + footerBarPaddingTop = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM)) { + footerBarPaddingBottom = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_PADDING_START)) { + footerBarPaddingStart = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_PADDING_START); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_PADDING_END)) { + footerBarPaddingEnd = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_PADDING_END); + } updateFooterBarPadding( buttonContainer, - buttonContainer.getPaddingLeft(), + footerBarPaddingStart, footerBarPaddingTop, - buttonContainer.getPaddingRight(), + footerBarPaddingEnd, footerBarPaddingBottom); if (PartnerConfigHelper.get(context) @@ -339,10 +392,13 @@ public class FooterBarMixin implements Mixin { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_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) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -352,10 +408,9 @@ public class FooterBarMixin implements Mixin { FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); // update information for primary button. Need to update as long as the button inflated. primaryButtonId = button.getId(); - primaryDefaultTextColor = button.getTextColors(); + button.setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true); primaryButton = footerButton; primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; - onFooterButtonInflated(button, footerBarPrimaryBackgroundColor); onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); @@ -410,6 +465,10 @@ public class FooterBarMixin implements Mixin { : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + usePrimaryStyle + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) @@ -417,6 +476,7 @@ public class FooterBarMixin implements Mixin { usePrimaryStyle ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -426,7 +486,7 @@ public class FooterBarMixin implements Mixin { FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); // update information for secondary button. Need to update as long as the button inflated. secondaryButtonId = button.getId(); - secondaryDefaultTextColor = button.getTextColors(); + button.setPrimaryButtonStyle(usePrimaryStyle); secondaryButton = footerButton; secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; @@ -448,6 +508,14 @@ public class FooterBarMixin implements Mixin { Button tempSecondaryButton = getSecondaryButtonView(); buttonContainer.removeAllViews(); + boolean isEvenlyWeightedButtons = isFooterButtonsEvenlyWeighted(); + boolean isLandscape = + context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + if (isLandscape && isEvenlyWeightedButtons && isFooterButtonAlignedEnd()) { + addSpace(); + } + if (tempSecondaryButton != null) { if (isSecondaryButtonInPrimaryStyle) { // Since the secondary button has the same style (with background) as the primary button, @@ -461,10 +529,30 @@ public class FooterBarMixin implements Mixin { } buttonContainer.addView(tempSecondaryButton); } - addSpace(); + if (!isFooterButtonAlignedEnd()) { + addSpace(); + } if (tempPrimaryButton != null) { buttonContainer.addView(tempPrimaryButton); } + + if (isEvenlyWeightedButtons) { + setEvenlyWeightedButtons(tempPrimaryButton, tempSecondaryButton); + } + } + + private void setEvenlyWeightedButtons(Button primaryButton, Button secondaryButton) { + if (primaryButton != null && secondaryButton != null) { + primaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + int primaryButtonMeasuredWidth = primaryButton.getMeasuredWidth(); + secondaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + + int secondaryButtonMeasuredWidth = secondaryButton.getMeasuredWidth(); + int maxButtonMeasureWidth = max(primaryButtonMeasuredWidth, secondaryButtonMeasuredWidth); + + primaryButton.getLayoutParams().width = maxButtonMeasureWidth; + secondaryButton.getLayoutParams().width = maxButtonMeasureWidth; + } } /** @@ -500,10 +588,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; } } @@ -616,22 +701,23 @@ public class FooterBarMixin implements Mixin { footerButtonPartnerConfig); if (!applyDynamicColor) { // adjust text color based on enabled state - updateButtonTextColorWithEnabledState( - button, footerButtonPartnerConfig.getButtonTextColorConfig()); + updateButtonTextColorWithStates( + button, + footerButtonPartnerConfig.getButtonTextColorConfig(), + footerButtonPartnerConfig.getButtonDisableTextColorConfig()); } } - private void updateButtonTextColorWithEnabledState( - Button button, PartnerConfig buttonTextColorConfig) { + private void updateButtonTextColorWithStates( + Button button, + PartnerConfig buttonTextColorConfig, + PartnerConfig buttonTextDisabledColorConfig) { if (button.isEnabled()) { FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( context, button, buttonTextColorConfig); } else { - FooterButtonStyleUtils.updateButtonTextDisableColor( - button, - /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle) - ? primaryDefaultTextColor - : secondaryDefaultTextColor); + FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig( + context, button, buttonTextDisabledColorConfig); } } diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java index ef45b5c..476d45b 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java +++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java @@ -32,19 +32,22 @@ import android.os.Build; import android.os.Build.VERSION_CODES; import android.util.StateSet; 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; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import java.util.HashMap; /** Utils for updating the button style. */ public class FooterButtonStyleUtils { private static final float DEFAULT_DISABLED_ALPHA = 0.26f; + private static final HashMap<Integer, ColorStateList> defaultTextColor = new HashMap<>(); + /** Apply the partner primary button style to given {@code button}. */ public static void applyPrimaryButtonPartnerResource( Context context, Button button, boolean applyDynamicColor) { @@ -55,9 +58,12 @@ public class FooterButtonStyleUtils { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -89,9 +95,12 @@ public class FooterButtonStyleUtils { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -112,6 +121,9 @@ public class FooterButtonStyleUtils { boolean isButtonIconAtEnd, FooterButtonPartnerConfig footerButtonPartnerConfig) { + // Save defualt text color for the partner config disable button text color not available. + saveButtonDefaultTextColor(button); + // If dynamic color enabled, these colors won't be overrode by partner config. // Instead, these colors align with the current theme colors. if (!applyDynamicColor) { @@ -119,6 +131,9 @@ public class FooterButtonStyleUtils { if (button.isEnabled()) { FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( context, button, footerButtonPartnerConfig.getButtonTextColorConfig()); + } else { + FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig( + context, button, footerButtonPartnerConfig.getButtonDisableTextColorConfig()); } FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig( context, @@ -133,6 +148,8 @@ public class FooterButtonStyleUtils { applyDynamicColor, footerButtonPartnerConfig.getButtonTextColorConfig(), footerButtonPartnerConfig.getButtonRippleColorAlphaConfig()); + FooterButtonStyleUtils.updateButtonMarginStartWithPartnerConfig( + context, button, footerButtonPartnerConfig.getButtonMarginStartConfig()); FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig( context, button, footerButtonPartnerConfig.getButtonTextSizeConfig()); FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig( @@ -161,10 +178,24 @@ public class FooterButtonStyleUtils { } } - static void updateButtonTextDisableColor(Button button, ColorStateList disabledTextColor) { - // TODO : add disable footer button text color partner config + static void updateButtonTextDisabledColorWithPartnerConfig( + Context context, Button button, PartnerConfig buttonDisableTextColorConfig) { + if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonDisableTextColorConfig)) { + @ColorInt + int color = PartnerConfigHelper.get(context).getColor(context, buttonDisableTextColorConfig); + updateButtonTextDisabledColor(button, color); + } else { + updateButtonTextDisableDefaultColor(button, getButtonDefaultTextCorlor(button)); + } + } + + static void updateButtonTextDisabledColor(Button button, @ColorInt int textColor) { + if (textColor != Color.TRANSPARENT) { + button.setTextColor(ColorStateList.valueOf(textColor)); + } + } - // disable state will use the default disable state color + static void updateButtonTextDisableDefaultColor(Button button, ColorStateList disabledTextColor) { button.setTextColor(disabledTextColor); } @@ -266,16 +297,31 @@ public class FooterButtonStyleUtils { } int[] pressedState = {android.R.attr.state_pressed}; + int[] focusState = {android.R.attr.state_focused}; + int argbColor = convertRgbToArgb(textColor, rippleAlpha); // Set text color for ripple. ColorStateList colorStateList = new ColorStateList( - new int[][] {pressedState, StateSet.NOTHING}, - new int[] {convertRgbToArgb(textColor, rippleAlpha), Color.TRANSPARENT}); + new int[][] {pressedState, focusState, StateSet.NOTHING}, + new int[] {argbColor, argbColor, Color.TRANSPARENT}); rippleDrawable.setColor(colorStateList); } } + static void updateButtonMarginStartWithPartnerConfig( + Context context, Button button, PartnerConfig buttonMarginStartConfig) { + ViewGroup.LayoutParams lp = button.getLayoutParams(); + boolean partnerConfigAvailable = + PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonMarginStartConfig); + if (partnerConfigAvailable && lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + int startMargin = + (int) PartnerConfigHelper.get(context).getDimension(context, buttonMarginStartConfig); + mlp.setMargins(startMargin, mlp.topMargin, mlp.rightMargin, mlp.bottomMargin); + } + } + static void updateButtonTextSizeWithPartnerConfig( Context context, Button button, PartnerConfig buttonTextSizeConfig) { float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig); @@ -367,7 +413,22 @@ public class FooterButtonStyleUtils { button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP); } - @VisibleForTesting + private static void saveButtonDefaultTextColor(Button button) { + defaultTextColor.put(button.getId(), button.getTextColors()); + } + + private static ColorStateList getButtonDefaultTextCorlor(Button button) { + if (!defaultTextColor.containsKey(button.getId())) { + throw new IllegalStateException("There is no saved default color for button"); + } + return defaultTextColor.get(button.getId()); + } + + static void clearSavedDefaultTextColor() { + defaultTextColor.clear(); + } + + /** 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 ea54745..8fa862f 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -17,6 +17,7 @@ package com.google.android.setupcompat.util; import android.os.Build; +import androidx.annotation.ChecksSdkIntAtLeast; /** * An util class to check whether the current OS version is higher or equal to sdk version of @@ -25,6 +26,25 @@ import android.os.Build; public final class BuildCompatUtils { /** + * Implementation of BuildCompat.isAtLeastR() suitable for use in Setup + * + * @return Whether the current OS version is higher or equal to R. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) + public static boolean isAtLeastR() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; + } + /** + * Implementation of BuildCompat.isAtLeastS() suitable for use in Setup + * + * @return Whether the current OS version is higher or equal to S. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) + public static boolean isAtLeastS() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + } + + /** * Implementation of BuildCompat.isAtLeast*() suitable for use in Setup * * <p>BuildCompat.isAtLeast*() can be changed by Android Release team, and once that is changed it @@ -40,26 +60,26 @@ public final class BuildCompatUtils { * <p>Supported configurations: * * <ul> - * <li>For current Android release: while new API is not finalized yet (CODENAME = "S", SDK_INT - * = 30|31) - * <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 31) - * <li>For next Android release (CODENAME = "T", SDK_INT = 30+) + * <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.S cannot be used here until final SDK is available in all - * Google3 channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API - * finalization. + * <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. * - * @return Whether the current OS version is higher or equal to S. + * @return Whether the current OS version is higher or equal to T. */ - public static boolean isAtLeastS() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + public static boolean isAtLeastT() { + if (!isAtLeastS()) { return false; } - return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 31) + return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33) || (Build.VERSION.CODENAME.length() == 1 - && Build.VERSION.CODENAME.charAt(0) >= 'S' - && Build.VERSION.CODENAME.charAt(0) <= 'Z'); + && Build.VERSION.CODENAME.charAt(0) >= 'T' + && 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 79976bc..90de25e 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -65,6 +65,9 @@ public final class WizardManagerHelper { */ public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; + /** Extra for notifying an activity that was called from suggested action activity. */ + public static final String EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW = "isSuwSuggestedActionFlow"; + public static final String EXTRA_THEME = "theme"; public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; @@ -122,7 +125,8 @@ public final class WizardManagerHelper { EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP, EXTRA_IS_PORTAL_SETUP, - EXTRA_IS_SETUP_FLOW)) { + EXTRA_IS_SETUP_FLOW, + EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW)) { dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); } @@ -182,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/java/com/google/android/setupcompat/view/ButtonBarLayout.java b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java index da1ab34..1157fae 100644 --- a/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java +++ b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java @@ -18,9 +18,12 @@ package com.google.android.setupcompat.view; import android.content.Context; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; import com.google.android.setupcompat.R; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.template.FooterActionButton; /** * An extension of LinearLayout that automatically switches to vertical orientation when it can't @@ -62,7 +65,7 @@ public class ButtonBarLayout extends LinearLayout { super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); - if (getMeasuredWidth() > widthSize) { + if (!isFooterButtonsEventlyWeighted(getContext()) && (getMeasuredWidth() > widthSize)) { setStacked(true); // Measure again in the new orientation. @@ -86,6 +89,7 @@ public class ButtonBarLayout extends LinearLayout { if (stacked) { child.setTag(R.id.suc_customization_original_weight, childParams.weight); childParams.weight = 0; + childParams.leftMargin = 0; } else { Float weight = (Float) child.getTag(R.id.suc_customization_original_weight); if (weight != null) { @@ -103,6 +107,8 @@ public class ButtonBarLayout extends LinearLayout { } if (stacked) { + // When stacked, the buttons need to be kept in the center of the button bar. + setHorizontalGravity(Gravity.CENTER); // HACK: In the default button bar style, the left and right paddings are not // balanced to compensate for different alignment for borderless (left) button and // the raised (right) button. When it's stacked, we want the buttons to be centered, @@ -115,4 +121,28 @@ public class ButtonBarLayout extends LinearLayout { setPadding(originalPaddingLeft, getPaddingTop(), originalPaddingRight, getPaddingBottom()); } } + + private boolean isFooterButtonsEventlyWeighted(Context context) { + int childCount = getChildCount(); + int primayButtonCount = 0; + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof FooterActionButton) { + if (((FooterActionButton) child).isPrimaryButtonStyle()) { + primayButtonCount += 1; + } + } + } + if (primayButtonCount != 2) { + return false; + } + + // TODO: Support neutral button style in glif layout for phone and tablet + if (context.getResources().getConfiguration().smallestScreenWidthDp >= 600 + && PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context)) { + return true; + } else { + return false; + } + } } 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 280ab81..c9a1966 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java @@ -40,6 +40,14 @@ public enum PartnerConfig { // The min height of the footer buttons CONFIG_FOOTER_BAR_MIN_HEIGHT(PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, ResourceType.DIMENSION), + // The padding start of the footer bar + CONFIG_FOOTER_BAR_PADDING_START( + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_START, ResourceType.DIMENSION), + + // The padding end of the footer bar + CONFIG_FOOTER_BAR_PADDING_END( + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_END, ResourceType.DIMENSION), + // The same as "windowLightNavigationBar". If set true, the navigation bar icons will be drawn // such that it is compatible with a light navigation bar background. CONFIG_LIGHT_NAVIGATION_BAR(PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR, ResourceType.BOOL), @@ -108,6 +116,10 @@ public enum PartnerConfig { CONFIG_FOOTER_BUTTON_MIN_HEIGHT( PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, ResourceType.DIMENSION), + // Make the footer buttons all aligned the end + CONFIG_FOOTER_BUTTON_ALIGNED_END( + PartnerConfigKey.KEY_FOOTER_BUTTON_ALIGNED_END, ResourceType.BOOL), + // Disabled background alpha of the footer buttons CONFIG_FOOTER_BUTTON_DISABLED_ALPHA( PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, ResourceType.FRACTION), @@ -116,6 +128,14 @@ public enum PartnerConfig { CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR( PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR, ResourceType.COLOR), + // Disabled text color of the primary footer button + CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR( + PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR), + + // Disabled text color of the secondary footer button + CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR( + PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR), + // Background color of the primary footer button CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR( PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR), @@ -124,6 +144,10 @@ public enum PartnerConfig { CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR( PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), + // Margin start of the primary footer button + CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START( + PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START, ResourceType.DIMENSION), + // Background color of the secondary footer button CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR( PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, ResourceType.COLOR), @@ -132,6 +156,10 @@ public enum PartnerConfig { CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR( PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), + // Margin start of the secondary footer button + CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START( + PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START, ResourceType.DIMENSION), + // Background color of layout CONFIG_LAYOUT_BACKGROUND_COLOR(PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, ResourceType.COLOR), @@ -141,6 +169,10 @@ public enum PartnerConfig { // Margin end of the layout CONFIG_LAYOUT_MARGIN_END(PartnerConfigKey.KEY_LAYOUT_MARGIN_END, ResourceType.DIMENSION), + // Middle horizontal spacing of the landscape layout + CONFIG_LAND_MIDDLE_HORIZONTAL_SPACING( + PartnerConfigKey.KEY_LAND_MIDDLE_HORIZONTAL_SPACING, ResourceType.DIMENSION), + // Text color of the header CONFIG_HEADER_TEXT_COLOR(PartnerConfigKey.KEY_HEADER_TEXT_COLOR, ResourceType.COLOR), @@ -207,6 +239,10 @@ public enum PartnerConfig { // Font family of the description CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING), + // Font family of the link text + CONFIG_DESCRIPTION_LINK_FONT_FAMILY( + PartnerConfigKey.KEY_DESCRIPTION_LINK_FONT_FAMILY, ResourceType.STRING), + // Margin top of the description text CONFIG_DESCRIPTION_TEXT_MARGIN_TOP( PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, ResourceType.DIMENSION), @@ -291,11 +327,11 @@ public enum PartnerConfig { // The divider of list items are showing on the pages. CONFIG_ITEMS_DIVIDER_SHOWN(PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN, ResourceType.BOOL), - // The intrinsic width of the card view for foldabe/tablet. + // The intrinsic width of the card view for foldable/tablet. CONFIG_CARD_VIEW_INTRINSIC_WIDTH( PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH, ResourceType.DIMENSION), - // The intrinsic height of the card view for foldabe/tablet. + // The intrinsic height of the card view for foldable/tablet. CONFIG_CARD_VIEW_INTRINSIC_HEIGHT( PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT, ResourceType.DIMENSION), @@ -414,7 +450,23 @@ public enum PartnerConfig { // The height of the header of the loading layout. CONFIG_LOADING_LAYOUT_HEADER_HEIGHT( - PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION); + PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION), + + // Use the fullscreen style lottie animation. + 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), + + // The margin bottom of progress bar. + CONFIG_PROGRESS_BAR_MARGIN_BOTTOM( + PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM, ResourceType.DIMENSION); /** Resource type of the partner resources type. */ public enum ResourceType { diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 2ca8876..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,14 +61,27 @@ 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; + private static PartnerConfigHelper instance = null; @VisibleForTesting Bundle resultBundle = null; @@ -79,7 +93,16 @@ 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 + * config with testing environment. + */ + @VisibleForTesting public static int savedScreenHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + + @VisibleForTesting public static int savedScreenWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED; public static synchronized PartnerConfigHelper get(@NonNull Context context) { if (!isValidInstance(context)) { @@ -93,15 +116,21 @@ public class PartnerConfigHelper { if (instance == null) { savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; savedOrientation = currentConfig.orientation; + savedScreenWidth = currentConfig.screenWidthDp; + savedScreenHeight = currentConfig.screenHeightDp; return false; } else { - if (isSetupWizardDayNightEnabled(context) - && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) { + boolean uiModeChanged = + isSetupWizardDayNightEnabled(context) + && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; + if (uiModeChanged + || currentConfig.orientation != savedOrientation + || currentConfig.screenWidthDp != savedScreenWidth + || currentConfig.screenHeightDp != savedScreenHeight) { savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; - resetInstance(); - return false; - } else if (currentConfig.orientation != savedOrientation) { savedOrientation = currentConfig.orientation; + savedScreenHeight = currentConfig.screenHeightDp; + savedScreenWidth = currentConfig.screenWidthDp; resetInstance(); return false; } @@ -315,7 +344,7 @@ public class PartnerConfigHelper { result = resource.getBoolean(resId); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -364,7 +393,7 @@ public class PartnerConfigHelper { result = getDimensionFromTypedValue( context, (TypedValue) partnerResourceCache.get(resourceConfig)); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -408,7 +437,7 @@ public class PartnerConfigHelper { result = resource.getFraction(resId, 1, 1); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -441,7 +470,7 @@ public class PartnerConfigHelper { result = resource.getInteger(resId); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -503,6 +532,8 @@ public class PartnerConfigHelper { /* arg= */ null, /* extras= */ null); partnerResourceCache.clear(); + Log.i( + TAG, "PartnerConfigsBundle=" + (resultBundle != null ? resultBundle.size() : "(null)")); } catch (IllegalArgumentException | SecurityException exception) { Log.w(TAG, "Fail to get config from suw provider"); } @@ -517,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); } /** @@ -544,12 +576,52 @@ 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; } /** @@ -606,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) { @@ -629,6 +734,29 @@ public class PartnerConfigHelper { && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); } + /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ + public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { + if (applyNeutralButtonStyleBundle == null) { + try { + applyNeutralButtonStyleBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "Neutral button style supporting status unknown; return as false."); + applyNeutralButtonStyleBundle = null; + return false; + } + } + + return (applyNeutralButtonStyleBundle != null + && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); + } + @VisibleForTesting static Uri getContentUri() { return new Uri.Builder() diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java index c7444a5..9554ff3 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java @@ -31,6 +31,8 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR, PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR, PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_START, + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_END, PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY, PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER, PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, @@ -47,15 +49,21 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_STYLE, PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, + PartnerConfigKey.KEY_FOOTER_BUTTON_ALIGNED_END, PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR, PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, + PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START, + PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR, PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, + PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START, + PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR, PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, PartnerConfigKey.KEY_LAYOUT_MARGIN_START, PartnerConfigKey.KEY_LAYOUT_MARGIN_END, + PartnerConfigKey.KEY_LAND_MIDDLE_HORIZONTAL_SPACING, PartnerConfigKey.KEY_HEADER_TEXT_SIZE, PartnerConfigKey.KEY_HEADER_TEXT_COLOR, PartnerConfigKey.KEY_HEADER_FONT_FAMILY, @@ -75,6 +83,7 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_DESCRIPTION_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, + PartnerConfigKey.KEY_DESCRIPTION_LINK_FONT_FAMILY, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, @@ -128,6 +137,10 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END, 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, }) // TODO: can be removed and always reference PartnerConfig.getResourceName()? @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @@ -155,6 +168,12 @@ public @interface PartnerConfigKey { // The min height of the footer bar String KEY_FOOTER_BAR_MIN_HEIGHT = "setup_compat_footer_bar_min_height"; + // The padding start of the footer bar + String KEY_FOOTER_BAR_PADDING_START = "setup_compat_footer_bar_padding_start"; + + // The padding end of the footer bar + String KEY_FOOTER_BAR_PADDING_END = "setup_compat_footer_bar_padding_end"; + // The font face used in footer buttons. This must be a string reference to a font that is // available in the system. Font references (@font or @xml) are not allowed. String KEY_FOOTER_BUTTON_FONT_FAMILY = "setup_compat_footer_button_font_family"; @@ -204,6 +223,9 @@ public @interface PartnerConfigKey { // The min height of the footer buttons String KEY_FOOTER_BUTTON_MIN_HEIGHT = "setup_compat_footer_button_min_height"; + // Make the footer buttons all aligned the end + String KEY_FOOTER_BUTTON_ALIGNED_END = "setup_compat_footer_button_aligned_end"; + // Disabled background alpha of the footer buttons String KEY_FOOTER_BUTTON_DISABLED_ALPHA = "setup_compat_footer_button_disabled_alpha"; @@ -216,12 +238,26 @@ public @interface PartnerConfigKey { // Text color of the primary footer button String KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR = "setup_compat_footer_primary_button_text_color"; + // Margin start of the primary footer button + String KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START = "setup_compat_footer_primary_button_margin_start"; + + // Disabled text color of the primary footer button + String KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR = "setup_compat_primary_button_disabled_text_color"; + // Background color of the secondary footer button String KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR = "setup_compat_footer_secondary_button_bg_color"; // Text color of the secondary footer button String KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR = "setup_compat_footer_secondary_button_text_color"; + // Margin start of the secondary footer button + String KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START = + "setup_compat_footer_secondary_button_margin_start"; + + // Disabled text color of the secondary footer button + String KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR = + "setup_compat_secondary_button_disabled_text_color"; + // Background color of layout String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color"; @@ -231,6 +267,9 @@ public @interface PartnerConfigKey { // Margin end of the layout String KEY_LAYOUT_MARGIN_END = "setup_design_layout_margin_end"; + // Middle horizontal spacing of the landscape layout + String KEY_LAND_MIDDLE_HORIZONTAL_SPACING = "setup_design_land_middle_horizontal_spacing"; + // Text size of the header String KEY_HEADER_TEXT_SIZE = "setup_design_header_text_size"; @@ -290,6 +329,9 @@ public @interface PartnerConfigKey { // Font family of the description String KEY_DESCRIPTION_FONT_FAMILY = "setup_design_description_font_family"; + // Font family of the link text + String KEY_DESCRIPTION_LINK_FONT_FAMILY = "setup_design_description_link_font_family"; + // Margin top of the header text String KEY_DESCRIPTION_TEXT_MARGIN_TOP = "setup_design_description_text_margin_top"; @@ -362,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. @@ -474,4 +516,18 @@ public @interface PartnerConfigKey { // A height of the header of loading layout. String KEY_LOADING_LAYOUT_HEADER_HEIGHT = "loading_layout_header_height"; + + // Use the fullscreen style lottie animation. + 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"; + + // A margin bottom of the content frame of progress bar. + String KEY_PROGRESS_BAR_MARGIN_BOTTOM = "setup_design_progress_bar_margin_bottom"; } diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl new file mode 100644 index 0000000..2b83576 --- /dev/null +++ b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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.portal; + +import com.google.android.setupcompat.portal.IPortalProgressCallback; +import com.google.android.setupcompat.portal.TaskComponent; + +/** + * Declares the interface for portal used by GmsCore. + */ +interface ISetupNotificationServicePortalExtension { + IPortalProgressCallback registerTask(in TaskComponent component) = 1; + + boolean removeTask(String packageName, String taskName) = 2; +}
\ No newline at end of file diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl new file mode 100644 index 0000000..8a4dfb3 --- /dev/null +++ b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 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.portal; + +/** + * A class that represents how a persistent notification is to be presented to the user using the + * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }. + */ +parcelable TaskComponent;
\ No newline at end of file diff --git a/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java new file mode 100644 index 0000000..4f1b884 --- /dev/null +++ b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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.portal; + +/** Constant values used for PortalExtension */ +public class PortalExtensionConstants { + public static final String BIND_SERVICE_INTENT_ACTION = + "com.google.android.setupcompat.portal.SetupNotificationService.BIND_EXTENSION"; + + private PortalExtensionConstants() {} + ; +} diff --git a/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java new file mode 100644 index 0000000..16d35a3 --- /dev/null +++ b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 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.portal; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import com.google.android.setupcompat.internal.Preconditions; + +/** + * A class that represents how a persistent notification is to be presented to the user using the + * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }. + */ +public class TaskComponent implements Parcelable { + private final String packageName; + private final String taskName; + @StringRes private final int displayNameResId; + @DrawableRes private final int displayIconResId; + private final Intent itemClickIntent; + + private TaskComponent( + String packageName, + String taskName, + @StringRes int displayNameResId, + @DrawableRes int displayIconResId, + Intent itemClickIntent) { + this.packageName = packageName; + this.taskName = taskName; + this.displayNameResId = displayNameResId; + this.displayIconResId = displayIconResId; + this.itemClickIntent = itemClickIntent; + } + + /** Returns a new instance of {@link Builder}. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** Returns the package name where the service exist. */ + @NonNull + public String getPackageName() { + return packageName; + } + + /** Returns the service class name */ + @NonNull + public String getTaskName() { + return taskName; + } + + /** Returns the string resource id of display name. */ + @StringRes + public int getDisplayName() { + return displayNameResId; + } + + /** Returns the drawable resource id of display icon. */ + @DrawableRes + public int getDisplayIcon() { + return displayIconResId; + } + + /** Returns the Intent to start the user interface while progress item click. */ + public Intent getItemClickIntent() { + return itemClickIntent; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getPackageName()); + dest.writeString(getTaskName()); + dest.writeInt(getDisplayName()); + dest.writeInt(getDisplayIcon()); + dest.writeParcelable(getItemClickIntent(), 0); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<TaskComponent> CREATOR = + new Creator<TaskComponent>() { + @Override + public TaskComponent createFromParcel(Parcel in) { + return TaskComponent.newBuilder() + .setPackageName(in.readString()) + .setTaskName(in.readString()) + .setDisplayName(in.readInt()) + .setDisplayIcon(in.readInt()) + .setItemClickIntent(in.readParcelable(Intent.class.getClassLoader())) + .build(); + } + + @Override + public TaskComponent[] newArray(int size) { + return new TaskComponent[size]; + } + }; + + /** Builder class for {@link com.google.android.setupcompat.portal.TaskComponent} objects. */ + public static class Builder { + private String packageName; + private String taskName; + @StringRes private int displayNameResId; + @DrawableRes private int displayIconResId; + private Intent itemClickIntent; + + /** Sets the packages name which is the service exists */ + public Builder setPackageName(@NonNull String packageName) { + this.packageName = packageName; + return this; + } + + /** Sets a name to identify what task this progress is. */ + public Builder setTaskName(@NonNull String taskName) { + this.taskName = taskName; + return this; + } + + /** Sets the name which is displayed on PortalActivity */ + public Builder setDisplayName(@StringRes int displayNameResId) { + this.displayNameResId = displayNameResId; + return this; + } + + /** Sets the icon which is display on PortalActivity */ + public Builder setDisplayIcon(@DrawableRes int displayIconResId) { + this.displayIconResId = displayIconResId; + return this; + } + + public Builder setItemClickIntent(Intent itemClickIntent) { + this.itemClickIntent = itemClickIntent; + return this; + } + + public TaskComponent build() { + Preconditions.checkNotNull(packageName, "packageName cannot be null."); + Preconditions.checkNotNull(taskName, "serviceClass cannot be null."); + Preconditions.checkNotNull(itemClickIntent, "Item click intent cannot be null"); + Preconditions.checkArgument(displayNameResId != 0, "Invalidate resource id of display name"); + Preconditions.checkArgument(displayIconResId != 0, "Invalidate resource id of display icon"); + + return new TaskComponent( + packageName, taskName, displayNameResId, displayIconResId, itemClickIntent); + } + } +} 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; +} |