diff options
author | Xin Li <delphij@google.com> | 2022-08-15 22:02:55 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-08-15 22:02:55 -0700 |
commit | 53fb35126609678aa4a8df95d046b0fe07196322 (patch) | |
tree | e94f2f0f46cd6d06fc62643eaf5c64bbb8a7cea7 | |
parent | d49534499bbdcebabaccc3a05dc709cc6025d80b (diff) | |
parent | 8b591a2253bc542bd13ce6ea47aefd27d8a046dd (diff) | |
download | setupcompat-53fb35126609678aa4a8df95d046b0fe07196322.tar.gz |
DO NOT MERGE - Merge Android 13main-16k
Bug: 242648940
Merged-In: I84ad65b397ac38f30f2a4a8ad8ba384fbb50abdb
Change-Id: I4d09c5d6c64a3030e70ce591f1ab10f83bba0f02
18 files changed, 412 insertions, 122 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/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java index 149da54..ed9c0e3 100644 --- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java +++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java @@ -54,12 +54,23 @@ public class SetupCompatServiceInvoker { public void bindBack(String screenName, Bundle bundle) { try { - setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle)); + loggingExecutor.execute(() -> invokeBindBack(screenName, bundle)); } catch (RejectedExecutionException e) { LOG.e(String.format("Screen %s bind back fail.", screenName), e); } } + /** + * Help invoke the {@link ISetupCompatService#onFocusStatusChanged} using {@code loggingExecutor}. + */ + public void onFocusStatusChanged(String screenName, Bundle bundle) { + try { + loggingExecutor.execute(() -> invokeOnWindowFocusChanged(screenName, bundle)); + } catch (RejectedExecutionException e) { + LOG.e(String.format("Screen %s report focus changed failed.", screenName), e); + } + } + private void invokeLogMetric( @MetricType int metricType, @SuppressWarnings("unused") Bundle args) { try { @@ -76,6 +87,29 @@ public class SetupCompatServiceInvoker { } } + private void invokeOnWindowFocusChanged(String screenName, Bundle bundle) { + try { + ISetupCompatService setupCompatService = + SetupCompatServiceProvider.get( + context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS); + if (setupCompatService != null) { + setupCompatService.onFocusStatusChanged(bundle); + } else { + LOG.w( + "Report focusChange failed since service reference is null. Are the permission valid?"); + } + } catch (InterruptedException + | TimeoutException + | RemoteException + | UnsupportedOperationException e) { + LOG.e( + String.format( + "Exception occurred while %s trying report windowFocusChange to SetupWizard.", + screenName), + e); + } + } + private void invokeBindBack(String screenName, Bundle bundle) { try { ISetupCompatService setupCompatService = @@ -96,14 +130,12 @@ public class SetupCompatServiceInvoker { private SetupCompatServiceInvoker(Context context) { this.context = context; this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get(); - this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get(); this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS; } private final Context context; private final ExecutorService loggingExecutor; - private final ExecutorService setupCompatExecutor; private final long waitTimeInMillisForServiceConnection; public static synchronized SetupCompatServiceInvoker get(Context context) { diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java index e75d991..e259f22 100644 --- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java +++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java @@ -82,8 +82,8 @@ public class SetupCompatServiceProvider { return waitForConnection(timeout, timeUnit); case NOT_STARTED: - throw new IllegalStateException( - "NOT_STARTED state only possible before instance is created."); + LOG.w("NOT_STARTED state only possible before instance is created."); + return null; } throw new IllegalStateException("Unknown state = " + serviceContext.state); } @@ -172,7 +172,8 @@ public class SetupCompatServiceProvider { return serviceContext; } - private void swapServiceContextAndNotify(ServiceContext latestServiceContext) { + @VisibleForTesting + void swapServiceContextAndNotify(ServiceContext latestServiceContext) { LOG.atInfo( String.format("State changed: %s -> %s", serviceContext.state, latestServiceContext.state)); @@ -200,7 +201,7 @@ public class SetupCompatServiceProvider { return countDownLatch; } countDownLatch = createCountDownLatch(); - } while (!connectedConditionRef.compareAndSet(/* expect= */ null, countDownLatch)); + } while (!connectedConditionRef.compareAndSet(/* expectedValue= */ null, countDownLatch)); return countDownLatch; } @@ -285,7 +286,8 @@ public class SetupCompatServiceProvider { REBIND_REQUIRED } - private static final class ServiceContext { + @VisibleForTesting + static final class ServiceContext { final State state; @Nullable final ISetupCompatService compatService; @@ -298,7 +300,8 @@ public class SetupCompatServiceProvider { } } - private ServiceContext(State state) { + @VisibleForTesting + ServiceContext(State state) { this(state, /* compatService= */ null); } } diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java deleted file mode 100644 index 2aa1240..0000000 --- a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.setupcompat.logging.internal; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.os.PersistableBundle; -import androidx.annotation.VisibleForTesting; -import com.google.android.setupcompat.logging.CustomEvent; -import com.google.android.setupcompat.logging.MetricKey; -import com.google.android.setupcompat.logging.SetupMetricsLogger; - -/** Uses to log internal event customization resource list. */ -@TargetApi(VERSION_CODES.Q) -public class PartnerCustomizedResourceListMetric { - - public static void logMetrics(Context context, String deviceDisplayName, Bundle bundle) { - PersistableBundle logBundle = - buildLogBundleFromResourceConfigBundle(context.getPackageName(), deviceDisplayName, bundle); - if (!logBundle.isEmpty()) { - SetupMetricsLogger.logCustomEvent( - context, - CustomEvent.create( - MetricKey.get("PartnerCustomizationResource", "NoScreenName"), logBundle)); - } - } - - @VisibleForTesting - public static PersistableBundle buildLogBundleFromResourceConfigBundle( - String defaultPackageName, String deviceDisplayName, Bundle resourceConfigBundle) { - PersistableBundle persistableBundle = new PersistableBundle(); - persistableBundle.putString("deviceDisplayName", deviceDisplayName); - for (String key : resourceConfigBundle.keySet()) { - Bundle resourceExtra = resourceConfigBundle.getBundle(key); - if (!resourceExtra.getString("packageName", defaultPackageName).equals(defaultPackageName)) { - persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), true); - } else { - persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), false); - } - } - - return persistableBundle; - } -} diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 6d92c40..4d2a0c9 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -17,6 +17,7 @@ package com.google.android.setupcompat.template; import static com.google.android.setupcompat.internal.Preconditions.ensureOnMainThread; +import static java.lang.Math.max; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -73,7 +74,7 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; - @VisibleForTesting LinearLayout buttonContainer; + @VisibleForTesting public LinearLayout buttonContainer; private FooterButton primaryButton; private FooterButton secondaryButton; @IdRes private int primaryButtonId; @@ -226,7 +227,7 @@ public class FooterBarMixin implements Mixin { FooterButtonStyleUtils.clearSavedDefaultTextColor(); } - private boolean isFooterButtonAlignedEnd() { + protected boolean isFooterButtonAlignedEnd() { if (PartnerConfigHelper.get(context) .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END)) { return PartnerConfigHelper.get(context) @@ -240,18 +241,16 @@ public class FooterBarMixin implements Mixin { if (!isSecondaryButtonInPrimaryStyle) { return false; } - // TODO: Support neutral button style in glif layout for phone and tablet PartnerConfigHelper.get(context); - return context.getResources().getConfiguration().smallestScreenWidthDp >= 600 - && PartnerConfigHelper.isNeutralButtonStyleEnabled(context); + return PartnerConfigHelper.isNeutralButtonStyleEnabled(context); } private View addSpace() { - LinearLayout buttonContainer = ensureFooterInflated(); + LinearLayout buttonContainerlayout = ensureFooterInflated(); View space = new View(context); space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); space.setVisibility(View.INVISIBLE); - buttonContainer.addView(space); + buttonContainerlayout.addView(space); return space; } @@ -531,8 +530,7 @@ public class FooterBarMixin implements Mixin { } buttonContainer.addView(tempSecondaryButton); } - if (!isFooterButtonAlignedEnd() - && (!isEvenlyWeightedButtons || (isEvenlyWeightedButtons && isLandscape))) { + if (!isFooterButtonAlignedEnd()) { addSpace(); } if (tempPrimaryButton != null) { @@ -545,21 +543,15 @@ public class FooterBarMixin implements Mixin { private void setEvenlyWeightedButtons( Button primaryButton, Button secondaryButton, boolean isEvenlyWeighted) { if (primaryButton != null && secondaryButton != null && isEvenlyWeighted) { - LinearLayout.LayoutParams primaryLayoutParams = - (LinearLayout.LayoutParams) primaryButton.getLayoutParams(); - if (null != primaryLayoutParams) { - primaryLayoutParams.width = 0; - primaryLayoutParams.weight = 1.0f; - primaryButton.setLayoutParams(primaryLayoutParams); - } + primaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + int primaryButtonMeasuredWidth = primaryButton.getMeasuredWidth(); + secondaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - LinearLayout.LayoutParams secondaryLayoutParams = - (LinearLayout.LayoutParams) secondaryButton.getLayoutParams(); - if (null != secondaryLayoutParams) { - secondaryLayoutParams.width = 0; - secondaryLayoutParams.weight = 1.0f; - secondaryButton.setLayoutParams(secondaryLayoutParams); - } + int secondaryButtonMeasuredWidth = secondaryButton.getMeasuredWidth(); + int maxButtonMeasureWidth = max(primaryButtonMeasuredWidth, secondaryButtonMeasuredWidth); + + primaryButton.getLayoutParams().width = maxButtonMeasureWidth; + secondaryButton.getLayoutParams().width = maxButtonMeasureWidth; } else { if (primaryButton != null) { LinearLayout.LayoutParams primaryLayoutParams = @@ -615,10 +607,7 @@ public class FooterBarMixin implements Mixin { int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig); if (color == Color.TRANSPARENT) { overrideTheme = R.style.SucPartnerCustomizationButton_Secondary; - } else if (color != Color.TRANSPARENT) { - // TODO: remove the constrain (color != Color.WHITE), need to check all pages - // go well without customization. It should be fine since the default value of secondary bg - // color is set as transparent. + } else { overrideTheme = R.style.SucPartnerCustomizationButton_Primary; } } diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java index ef2aa6b..476d45b 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java +++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java @@ -35,7 +35,6 @@ import android.util.TypedValue; import android.view.ViewGroup; import android.widget.Button; import androidx.annotation.ColorInt; -import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.R; import com.google.android.setupcompat.internal.FooterButtonPartnerConfig; import com.google.android.setupcompat.internal.Preconditions; @@ -429,7 +428,7 @@ public class FooterButtonStyleUtils { defaultTextColor.clear(); } - @VisibleForTesting + /** Gets {@code GradientDrawable} from given {@code button}. */ public static GradientDrawable getGradientDrawable(Button button) { // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index 5b7c3ad..3c4e2a2 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -60,26 +60,26 @@ public final class BuildCompatUtils { * <p>Supported configurations: * * <ul> - * <li>For current Android release: while new API is not finalized yet (CODENAME = "T", SDK_INT - * = 33) + * <li>For current Android release: while new API is not finalized yet (CODENAME = "Tiramisu", + * SDK_INT = 33) * <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 32) * <li>For next Android release (CODENAME = "U", SDK_INT = 34+) * </ul> * * <p>Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all - * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API - * finalization. + * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API finalization. * * @return Whether the current OS version is higher or equal to T. */ public static boolean isAtLeastT() { - if (!isAtLeastS()) { - return false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return true; } return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33) || (Build.VERSION.CODENAME.length() == 1 && Build.VERSION.CODENAME.charAt(0) >= 'T' - && Build.VERSION.CODENAME.charAt(0) <= 'Z'); + && Build.VERSION.CODENAME.charAt(0) <= 'Z') + || (Build.VERSION.CODENAME.equals("Tiramisu") && Build.VERSION.SDK_INT >= 32); } private BuildCompatUtils() {} diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 84bd68b..90de25e 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -186,6 +186,17 @@ public final class WizardManagerHelper { } /** + * Checks whether an intent is running in the portal setup wizard flow. + * + * @param originalIntent The original intent that was used to start the step, usually via {@link + * Activity#getIntent()}. + * @return true if the intent passed in was running in portal setup wizard. + */ + public static boolean isPortalSetupWizard(Intent originalIntent) { + return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_PORTAL_SETUP, false); + } + + /** * Checks whether an intent is running in the deferred setup wizard flow. * * @param originalIntent The original intent that was used to start the step, usually via {@link diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml index 6474426..6625e83 100644 --- a/main/res/values/styles.xml +++ b/main/res/values/styles.xml @@ -45,13 +45,13 @@ <item name="android:theme">@style/SucPartnerCustomizationButton.Primary</item> <!-- Values used in styles --> - <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item> + <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</item> <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item> <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item> <item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item> <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item> <item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item> - <item name="android:stateListAnimator">@null</item> + <item name="android:stateListAnimator" tools:ignore="NewApi">@null</item> <!-- Values used in themes --> <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/sucFooterBarButtonCornerRadius</item> @@ -65,7 +65,7 @@ <item name="android:theme">@style/SucPartnerCustomizationButton.Secondary</item> <!-- Values used in styles --> - <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item> + <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</item> <item name="android:minWidth">0dp</item> <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item> <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item> diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java index 442e86c..c9a1966 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java @@ -456,6 +456,10 @@ public enum PartnerConfig { CONFIG_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED( PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, ResourceType.BOOL), + // Waiting for the animation finished before process to the next page/action. + CONFIG_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED( + PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED, ResourceType.BOOL), + // The margin top of progress bar. CONFIG_PROGRESS_BAR_MARGIN_TOP( PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, ResourceType.DIMENSION), diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 3525fa1..aca9a07 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -35,6 +35,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType; +import com.google.android.setupcompat.util.BuildCompatUtils; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; @@ -60,15 +61,23 @@ public class PartnerConfigHelper { "isExtendedPartnerConfigEnabled"; @VisibleForTesting + public static final String IS_MATERIAL_YOU_STYLE_ENABLED_METHOD = "IsMaterialYouStyleEnabled"; + + @VisibleForTesting public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled"; @VisibleForTesting public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; + @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard"; + @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you"; + @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; + @VisibleForTesting public static Bundle applyMaterialYouConfigBundle = null; + @VisibleForTesting public static Bundle applyDynamicColorBundle = null; @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; @@ -84,7 +93,8 @@ public class PartnerConfigHelper { private static int savedConfigUiMode; - private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + @VisibleForTesting + public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; /** * When testing related to fake PartnerConfigHelper instance, should sync the following saved @@ -538,9 +548,10 @@ public class PartnerConfigHelper { if (fallbackBundle != null) { resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName)); } - - return adjustResourceEntryDayNightMode( - context, ResourceEntry.fromBundle(context, resourceEntryBundle)); + ResourceEntry adjustResourceEntry = + adjustResourceEntryDefaultValue( + context, ResourceEntry.fromBundle(context, resourceEntryBundle)); + return adjustResourceEntryDayNightMode(context, adjustResourceEntry); } /** @@ -565,11 +576,50 @@ public class PartnerConfigHelper { return resourceEntry; } + // Check the MNStyle flag and replace the inputResourceEntry.resourceName & + // inputResourceEntry.resourceId after T, that means if using Gliv4 before S, will always use + // glifv3 resources. + ResourceEntry adjustResourceEntryDefaultValue(Context context, ResourceEntry inputResourceEntry) { + if (BuildCompatUtils.isAtLeastT() && shouldApplyMaterialYouStyle(context)) { + // If not overlay resource + try { + if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) { + String resourceTypeName = + inputResourceEntry + .getResources() + .getResourceTypeName(inputResourceEntry.getResourceId()); + // try to update resourceName & resourceId + String materialYouResourceName = + inputResourceEntry.getResourceName().concat(MATERIAL_YOU_RESOURCE_SUFFIX); + int materialYouResourceId = + inputResourceEntry + .getResources() + .getIdentifier( + materialYouResourceName, + resourceTypeName, + inputResourceEntry.getPackageName()); + if (materialYouResourceId != 0) { + Log.i(TAG, "use material you resource:" + materialYouResourceName); + return new ResourceEntry( + inputResourceEntry.getPackageName(), + materialYouResourceName, + materialYouResourceId, + inputResourceEntry.getResources()); + } + } + } catch (NotFoundException ex) { + // fall through + } + } + return inputResourceEntry; + } + @VisibleForTesting public static synchronized void resetInstance() { instance = null; suwDayNightEnabledBundle = null; applyExtendedPartnerConfigBundle = null; + applyMaterialYouConfigBundle = null; applyDynamicColorBundle = null; applyNeutralButtonStyleBundle = null; } @@ -628,6 +678,39 @@ public class PartnerConfigHelper { IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false)); } + /** + * Returns true if the SetupWizard is flow enabled "Material You(Glifv4)" style, or the result of + * shouldApplyExtendedPartnerConfig() in SDK S as fallback. + */ + public static boolean shouldApplyMaterialYouStyle(@NonNull Context context) { + if (applyMaterialYouConfigBundle == null || applyMaterialYouConfigBundle.isEmpty()) { + try { + applyMaterialYouConfigBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + // The suw version did not support the flag yet, fallback to + // shouldApplyExtendedPartnerConfig() for SDK S. + if (applyMaterialYouConfigBundle != null + && applyMaterialYouConfigBundle.isEmpty() + && !BuildCompatUtils.isAtLeastT()) { + return shouldApplyExtendedPartnerConfig(context); + } + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "SetupWizard Material You configs supporting status unknown; return as false."); + applyMaterialYouConfigBundle = null; + return false; + } + } + + return (applyMaterialYouConfigBundle != null + && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_ENABLED_METHOD, false)); + } + /** Returns true if the SetupWizard supports the dynamic color during setup flow. */ public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) { if (applyDynamicColorBundle == null) { diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java index cbf72f5..9554ff3 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java @@ -138,6 +138,7 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM, PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, + PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED, PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM, }) @@ -403,10 +404,10 @@ public @interface PartnerConfigKey { // The divider of list items are showing. String KEY_ITEMS_DIVIDER_SHOWN = "setup_design_items_divider_shown"; - // The intrinsic width of the card view for foldabe/tablet. + // The intrinsic width of the card view for foldable/tablet. String KEY_CARD_VIEW_INTRINSIC_WIDTH = "setup_design_card_view_intrinsic_width"; - // The intrinsic height of the card view for foldabe/tablet. + // The intrinsic height of the card view for foldable/tablet. String KEY_CARD_VIEW_INTRINSIC_HEIGHT = "setup_design_card_view_intrinsic_height"; // The animation of loading screen used in those activities which is non of below type. @@ -520,6 +521,10 @@ public @interface PartnerConfigKey { String KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED = "loading_layout_full_screen_illustration_enabled"; + // Waiting the animation finished before process to the next page/action. + String KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED = + "loading_layout_wait_for_animation_finished"; + // A margin top of the content frame of progress bar. String KEY_PROGRESS_BAR_MARGIN_TOP = "setup_design_progress_bar_margin_top"; diff --git a/setup_extension/info_modules.proto b/setup_extension/info_modules.proto new file mode 100644 index 0000000..5027c4c --- /dev/null +++ b/setup_extension/info_modules.proto @@ -0,0 +1,96 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: yuchaoyu@google.com (Yu Chao) + +syntax = "proto2"; + +package portal_tips_proto; + +option java_package = "com.google.android.setupcompat"; +option java_outer_classname = "InfoModules"; + +// A list of informational modules to be displayed under the deferred setup +// items. +// Next index: 5 +message InfoModuleList { + // Contains the title of the entire info module section. This is displayed + // before the subtitle and the list of info modules. + optional string section_title = 1; + + // Contains the subtitle of the entire info module section. This is displayed + // before the list of info modules. + optional string section_subtitle = 2; + + // A module represents a single informational item to be displayed in the list + // under the deferred setup page. + // + // Each module contains a unique id, three sections of text labels (title, + // description, footer), the Intent URI to launch when being clicked, the raw + // icon, and a boolean to determine whether or not the icon should be colored. + // Next index: 8 + message InfoModule { + + // The id to uniquely identify an InfoModule. + optional string id = 1; + + // Contains the title of the module. This is displayed as the first section + // of the text content. + optional string title = 2; + + // Contains the main paragraphs of the module. This is displayed as the + // second section of the text content. + optional string description = 3; + + // Contains the footer text of the module. This is displayed as the third + // and final section of the text content. + optional string footer = 4; + + // Contains the Intent string to be launched when the module is clicked. + // + // This string could be generated from an `Intent` object through the + // `toUri()` method. + optional string intent_uri = 5; + + // Contains the icon of the module in raw bytes of bitmap format. + optional bytes raw_icon = 6; + + // Determines whether the icon should be colored according to the theme. + // Generally speaking, this should be set to true for monoline icons (such + // as Material Icons) because their colors change according to the theme. + // For icons which their colors should be preserved (such as package icons), + // this should be set to false. + optional bool should_apply_theme_color_on_icon = 7; + } + + // Contains a list of informational modules to be displayed. + repeated InfoModule info_module_list = 3; + + // A trailing link is a button item appearing below the info module list. + // Next index: 3 + message TrailingLink { + // Contains the text that should be displayed on the link button. + optional string text = 1; + + // Contains the Intent string to be launched when the link button is + // clicked. + // + // This string could be generated from an `Intent` object through the + // `toUri()` method. + optional string intent_uri = 2; + } + + // Contains a trailing link to be displayed below the info modules. + optional TrailingLink trailing_link = 4; +} |