diff options
author | Xin Li <delphij@google.com> | 2020-08-27 10:16:31 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-08-27 10:16:31 -0700 |
commit | 3fec5d8276969fd44b7f0b49ddb3afcb9dccdc3e (patch) | |
tree | 201c5ea5c4def030650fab9c712ea41bf0ac74e9 | |
parent | 4a6e4e804f30dfac6c250685a3453e8fb5c2ee32 (diff) | |
parent | 93f6d15b4a806cf4465e256a041e0b91c9aab3fe (diff) | |
download | setupcompat-temp_sam_168057903.tar.gz |
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)temp_sam_168057903
Bug: 166295507
Merged-In: Ic60d2d28c1488f6a602a8ac7b4268e722a0e386b
Change-Id: I904a49116f122667bf0a0ac3017e718962cb1f2d
16 files changed, 412 insertions, 85 deletions
diff --git a/build.gradle b/build.gradle index f4c7459..7398305 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 'android-Q' + // Not specifying compileSdkVersion here so clients can specify it; must be at least Q defaultConfig { minSdkVersion 14 diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 360a0a0..fac4b39 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -212,9 +212,7 @@ public class PartnerCustomizationLayout extends TemplateLayout { SetupMetricsLogger.logCustomEvent( getContext(), - CustomEvent.create( - MetricKey.get("SetupCompatMetrics", activity.getClass().getSimpleName()), - persistableBundle)); + CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle)); } } diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java index 6a019fd..39b50cf 100644 --- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java +++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java @@ -22,6 +22,8 @@ import com.google.android.setupcompat.template.FooterButton; /** Keep the partner configuration of a footer button. Used when the button is inflated. */ public class FooterButtonPartnerConfig { private final PartnerConfig buttonBackgroundConfig; + private final PartnerConfig buttonDisableAlphaConfig; + private final PartnerConfig buttonDisableBackgroundConfig; private final PartnerConfig buttonIconConfig; private final PartnerConfig buttonTextColorConfig; private final PartnerConfig buttonTextSizeConfig; @@ -33,6 +35,8 @@ public class FooterButtonPartnerConfig { private FooterButtonPartnerConfig( int partnerTheme, PartnerConfig buttonBackgroundConfig, + PartnerConfig buttonDisableAlphaConfig, + PartnerConfig buttonDisableBackgroundConfig, PartnerConfig buttonIconConfig, PartnerConfig buttonTextColorConfig, PartnerConfig buttonTextSizeConfig, @@ -45,6 +49,8 @@ public class FooterButtonPartnerConfig { this.buttonTextSizeConfig = buttonTextSizeConfig; this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig; this.buttonBackgroundConfig = buttonBackgroundConfig; + this.buttonDisableAlphaConfig = buttonDisableAlphaConfig; + this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig; this.buttonRadiusConfig = buttonRadiusConfig; this.buttonIconConfig = buttonIconConfig; this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig; @@ -58,6 +64,14 @@ public class FooterButtonPartnerConfig { return buttonBackgroundConfig; } + public PartnerConfig getButtonDisableAlphaConfig() { + return buttonDisableAlphaConfig; + } + + public PartnerConfig getButtonDisableBackgroundConfig() { + return buttonDisableBackgroundConfig; + } + public PartnerConfig getButtonIconConfig() { return buttonIconConfig; } @@ -86,6 +100,8 @@ public class FooterButtonPartnerConfig { public static class Builder { private final FooterButton footerButton; private PartnerConfig buttonBackgroundConfig = null; + private PartnerConfig buttonDisableAlphaConfig = null; + private PartnerConfig buttonDisableBackgroundConfig = null; private PartnerConfig buttonIconConfig = null; private PartnerConfig buttonTextColorConfig = null; private PartnerConfig buttonTextSizeConfig = null; @@ -105,6 +121,16 @@ public class FooterButtonPartnerConfig { return this; } + public Builder setButtonDisableAlphaConfig(PartnerConfig buttonDisableAlphaConfig) { + this.buttonDisableAlphaConfig = buttonDisableAlphaConfig; + return this; + } + + public Builder setButtonDisableBackgroundConfig(PartnerConfig buttonDisableBackgroundConfig) { + this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig; + return this; + } + public Builder setButtonIconConfig(PartnerConfig buttonIconConfig) { this.buttonIconConfig = buttonIconConfig; return this; @@ -144,6 +170,8 @@ public class FooterButtonPartnerConfig { return new FooterButtonPartnerConfig( partnerTheme, buttonBackgroundConfig, + buttonDisableAlphaConfig, + buttonDisableBackgroundConfig, buttonIconConfig, buttonTextColorConfig, buttonTextSizeConfig, diff --git a/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java index 269dd6d..d882c9d 100644 --- a/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java +++ b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java @@ -24,7 +24,9 @@ import android.app.FragmentManager; import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.os.PersistableBundle; import android.util.Log; +import com.google.android.setupcompat.logging.CustomEvent; import com.google.android.setupcompat.logging.MetricKey; import com.google.android.setupcompat.logging.SetupMetricsLogger; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -87,7 +89,7 @@ public class LifecycleFragment extends Fragment { @Override public void onAttach(Context context) { super.onAttach(context); - metricKey = MetricKey.get("ScreenDuration", getActivity().getClass().getSimpleName()); + metricKey = MetricKey.get("ScreenDuration", getActivity()); } @Override @@ -100,6 +102,7 @@ public class LifecycleFragment extends Fragment { public void onResume() { super.onResume(); startInNanos = ClockProvider.timeInNanos(); + logScreenResume(); } @Override @@ -107,4 +110,14 @@ public class LifecycleFragment extends Fragment { super.onPause(); durationInNanos += (ClockProvider.timeInNanos() - startInNanos); } + + private void logScreenResume() { + if (VERSION.SDK_INT >= VERSION_CODES.Q) { + PersistableBundle bundle = new PersistableBundle(); + bundle.putLong("onScreenResume", System.nanoTime()); + SetupMetricsLogger.logCustomEvent( + getActivity(), + CustomEvent.create(MetricKey.get("ScreenActivity", getActivity()), bundle)); + } + } } diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java index 66503a7..2043a81 100644 --- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java +++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java @@ -169,7 +169,7 @@ public class SetupCompatServiceProvider { return serviceContext.state; } - private ServiceContext getCurrentServiceState() { + private synchronized ServiceContext getCurrentServiceState() { return serviceContext; } diff --git a/main/java/com/google/android/setupcompat/logging/CustomEvent.java b/main/java/com/google/android/setupcompat/logging/CustomEvent.java index 88ac05e..38c32fa 100644 --- a/main/java/com/google/android/setupcompat/logging/CustomEvent.java +++ b/main/java/com/google/android/setupcompat/logging/CustomEvent.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.google.android.setupcompat.internal.ClockProvider; import com.google.android.setupcompat.internal.PersistableBundles; @@ -201,6 +202,19 @@ public final class CustomEvent implements Parcelable { } } + /** + * Trims the string longer than {@code MAX_STR_LENGTH} character, only keep the first {@code + * MAX_STR_LENGTH} - 1 characters and attached … in the end. + */ + @NonNull + public static String trimsStringOverMaxLength(@NonNull String str) { + if (str.length() <= MAX_STR_LENGTH) { + return str; + } else { + return String.format("%s…", str.substring(0, MAX_STR_LENGTH - 1)); + } + } + @VisibleForTesting static final int MAX_STR_LENGTH = 50; @VisibleForTesting static final int MIN_BUNDLE_KEY_LENGTH = 3; } diff --git a/main/java/com/google/android/setupcompat/logging/MetricKey.java b/main/java/com/google/android/setupcompat/logging/MetricKey.java index 125dee9..cdfb7d7 100644 --- a/main/java/com/google/android/setupcompat/logging/MetricKey.java +++ b/main/java/com/google/android/setupcompat/logging/MetricKey.java @@ -18,6 +18,7 @@ package com.google.android.setupcompat.logging; import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; +import android.app.Activity; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -40,6 +41,21 @@ public final class MetricKey implements Parcelable { /** * Creates a new instance of MetricKey. * + * @param name metric name to identify what we log + * @param activity activity of metric screen, uses to generate screenName + */ + public static MetricKey get(@NonNull String name, @NonNull Activity activity) { + String screenName = activity.getComponentName().getClassName(); + assertLengthInRange(name, "MetricKey.name", MIN_METRIC_KEY_LENGTH, MAX_METRIC_KEY_LENGTH); + Preconditions.checkArgument( + METRIC_KEY_PATTERN.matcher(name).matches(), + "Invalid MetricKey, only alpha numeric characters are allowed."); + return new MetricKey(name, screenName); + } + + /** + * Creates a new instance of MetricKey. + * * <p>NOTE: * * <ul> @@ -50,15 +66,21 @@ public final class MetricKey implements Parcelable { * </ul> */ public static MetricKey get(@NonNull String name, @NonNull String screenName) { + // We only checked the length of customized screen name, by the reason if the screenName match + // to the class name skip check it + if (!SCREEN_COMPONENTNAME_PATTERN.matcher(screenName).matches()) { + assertLengthInRange( + screenName, "MetricKey.screenName", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH); + Preconditions.checkArgument( + SCREEN_NAME_PATTERN.matcher(screenName).matches(), + "Invalid ScreenName, only alpha numeric characters are allowed."); + } + assertLengthInRange(name, "MetricKey.name", MIN_METRIC_KEY_LENGTH, MAX_METRIC_KEY_LENGTH); - assertLengthInRange( - screenName, "MetricKey.screenName", MIN_SCREEN_NAME_LENGTH, MAX_SCREEN_NAME_LENGTH); Preconditions.checkArgument( METRIC_KEY_PATTERN.matcher(name).matches(), "Invalid MetricKey, only alpha numeric characters are allowed."); - Preconditions.checkArgument( - METRIC_KEY_PATTERN.matcher(screenName).matches(), - "Invalid MetricKey, only alpha numeric characters are allowed."); + return new MetricKey(name, screenName); } @@ -145,4 +167,7 @@ public final class MetricKey implements Parcelable { private static final int MAX_SCREEN_NAME_LENGTH = 50; private static final int MAX_METRIC_KEY_LENGTH = 30; private static final Pattern METRIC_KEY_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]+"); + private static final Pattern SCREEN_COMPONENTNAME_PATTERN = + Pattern.compile("^([a-z]+[.])+[A-Z][a-zA-Z0-9]+"); + private static final Pattern SCREEN_NAME_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]+"); } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 3c88791..bc9e5c1 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -303,6 +303,8 @@ public class FooterBarMixin implements Mixin { /* buttonBackgroundColorConfig= */ PartnerConfig .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)) .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) + .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) + .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) @@ -357,6 +359,8 @@ public class FooterBarMixin implements Mixin { /* buttonBackgroundColorConfig= */ PartnerConfig .CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) + .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) + .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) @@ -428,14 +432,16 @@ public class FooterBarMixin implements Mixin { // TODO: Make sure customize attributes in theme can be applied during setup flow. // If sets background color to full transparent, the button changes to colored borderless ink // button style. - int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig); - if (applyPartnerResources && color == Color.TRANSPARENT) { - overrideTheme = R.style.SucPartnerCustomizationButton_Secondary; - } else if (applyPartnerResources && (color != Color.TRANSPARENT)) { - // TODO: remove the constrain (color != Color.WHITE), need to check all pages go - // well without customization. It should be fine since the default value of secondary bg color - // is set as transparent. - overrideTheme = R.style.SucPartnerCustomizationButton_Primary; + if (applyPartnerResources) { + 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. + overrideTheme = R.style.SucPartnerCustomizationButton_Primary; + } } return overrideTheme; } @@ -545,7 +551,10 @@ public class FooterBarMixin implements Mixin { updateButtonTypeFaceWithPartnerConfig( button, footerButtonPartnerConfig.getButtonTextTypeFaceConfig()); updateButtonBackgroundWithPartnerConfig( - button, footerButtonPartnerConfig.getButtonBackgroundConfig()); + button, + footerButtonPartnerConfig.getButtonBackgroundConfig(), + footerButtonPartnerConfig.getButtonDisableAlphaConfig(), + footerButtonPartnerConfig.getButtonDisableBackgroundConfig()); updateButtonRadiusWithPartnerConfig(button, footerButtonPartnerConfig.getButtonRadiusConfig()); updateButtonIconWithPartnerConfig(button, footerButtonPartnerConfig.getButtonIconConfig()); updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig); @@ -586,25 +595,43 @@ public class FooterBarMixin implements Mixin { @TargetApi(VERSION_CODES.Q) private void updateButtonBackgroundWithPartnerConfig( - Button button, PartnerConfig buttonBackgroundConfig) { + Button button, + PartnerConfig buttonBackgroundConfig, + PartnerConfig buttonDisableAlphaConfig, + PartnerConfig buttonDisableBackgroundConfig) { Preconditions.checkArgument( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, "Update button background only support on sdk Q or higher"); @ColorInt int color; + @ColorInt int disabledColor; + float disabledAlpha; int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled}; int[] ENABLED_STATE_SET = {}; color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundConfig); + disabledAlpha = + PartnerConfigHelper.get(context).getFraction(context, buttonDisableAlphaConfig, 0f); + disabledColor = + PartnerConfigHelper.get(context).getColor(context, buttonDisableBackgroundConfig); if (color != Color.TRANSPARENT) { - TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha}); - float alpha = a.getFloat(0, DEFAULT_DISABLED_ALPHA); - a.recycle(); + if (disabledAlpha <= 0f) { + // if no partner resource, fallback to theme disable alpha + float alpha; + TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha}); + alpha = a.getFloat(0, DEFAULT_DISABLED_ALPHA); + a.recycle(); + disabledAlpha = alpha; + } + if (disabledColor == Color.TRANSPARENT) { + // if no partner resource, fallback to button background color + disabledColor = color; + } // Set text color for ripple. ColorStateList colorStateList = new ColorStateList( new int[][] {DISABLED_STATE_SET, ENABLED_STATE_SET}, - new int[] {convertRgbToArgb(color, alpha), color}); + new int[] {convertRgbToArgb(disabledColor, disabledAlpha), color}); // b/129482013: When a LayerDrawable is mutated, a new clone of its children drawables are // created, but without copying the state from the parent drawable. So even though the diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java index a4d2c87..2fa8c7c 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterButton.java @@ -32,6 +32,7 @@ import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import com.google.android.setupcompat.R; +import com.google.android.setupcompat.logging.CustomEvent; import java.lang.annotation.Retention; /** @@ -285,7 +286,8 @@ public final class FooterButton implements OnClickListener { @TargetApi(VERSION_CODES.Q) public PersistableBundle getMetrics(String buttonName) { PersistableBundle bundle = new PersistableBundle(); - bundle.putString(buttonName + KEY_BUTTON_TEXT, getText().toString()); + bundle.putString( + buttonName + KEY_BUTTON_TEXT, CustomEvent.trimsStringOverMaxLength(getText().toString())); bundle.putString(buttonName + KEY_BUTTON_TYPE, getButtonTypeName()); bundle.putInt(buttonName + KEY_BUTTON_ON_CLICK_COUNT, clickCount); return bundle; diff --git a/main/java/com/google/android/setupcompat/util/ResultCodes.java b/main/java/com/google/android/setupcompat/util/ResultCodes.java index 3934b21..5fed731 100644 --- a/main/java/com/google/android/setupcompat/util/ResultCodes.java +++ b/main/java/com/google/android/setupcompat/util/ResultCodes.java @@ -24,6 +24,10 @@ public final class ResultCodes { public static final int RESULT_SKIP = RESULT_FIRST_USER; public static final int RESULT_RETRY = RESULT_FIRST_USER + 1; public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2; + public static final int RESULT_LIFECYCLE_NOT_MATCHED = RESULT_FIRST_USER + 3; + public static final int RESULT_FLOW_NOT_MATCHED = RESULT_FIRST_USER + 4; public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100; + + private ResultCodes() {} } diff --git a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java index f336617..75e5dd3 100644 --- a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java +++ b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java @@ -347,4 +347,6 @@ public final class SystemBarHelper { void onDecorViewInstalled(View decorView); } + + private SystemBarHelper() {} } diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 36b7d38..bfe1dbb 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -32,7 +32,7 @@ import java.util.Arrays; * shown inside the setup flow. This includes things like parsing extras passed by Wizard Manager, * and invoking Wizard Manager to start the next action. */ -public class WizardManagerHelper { +public final class WizardManagerHelper { private static final String ACTION_NEXT = "com.android.wizard.NEXT"; @@ -216,4 +216,6 @@ public class WizardManagerHelper { || isDeferredSetupWizard(originalIntent); } } + + private WizardManagerHelper() {} } diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java index 73d66e6..56256c9 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java @@ -93,6 +93,14 @@ public enum PartnerConfig { CONFIG_FOOTER_BUTTON_TEXT_SIZE( PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, ResourceType.DIMENSION), + // Disabled background alpha of the footer buttons + CONFIG_FOOTER_BUTTON_DISABLED_ALPHA( + PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, ResourceType.FRACTION), + + // Disabled background color of the footer buttons + CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR( + PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_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), @@ -139,15 +147,55 @@ public enum PartnerConfig { PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR, ResourceType.COLOR), // Font family of the description - CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING); + CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING), + + // Text size of the body content text + CONFIG_CONTENT_TEXT_SIZE(PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, ResourceType.DIMENSION), + + // Text color of the body content text + CONFIG_CONTENT_TEXT_COLOR(PartnerConfigKey.KEY_CONTENT_TEXT_COLOR, ResourceType.COLOR), + + // Link text color of the body content text + CONFIG_CONTENT_LINK_TEXT_COLOR(PartnerConfigKey.KEY_CONTENT_LINK_TEXT_COLOR, ResourceType.COLOR), + + // Font family of the body content text + CONFIG_CONTENT_FONT_FAMILY(PartnerConfigKey.KEY_CONTENT_FONT_FAMILY, ResourceType.STRING), + + // Gravity of the body content text + CONFIG_CONTENT_LAYOUT_GRAVITY(PartnerConfigKey.KEY_CONTENT_LAYOUT_GRAVITY, ResourceType.STRING), + + // The animation of loading screen used in those activities which is non of below type. + CONFIG_PROGRESS_ILLUSTRATION_DEFAULT( + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DEFAULT, ResourceType.ILLUSTRATION), + + // The animation of loading screen used in those activity which is processing account info or + // related functions. + // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT + CONFIG_PROGRESS_ILLUSTRATION_ACCOUNT( + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_ACCOUNT, ResourceType.ILLUSTRATION), + + // The animation of loading screen used in those activity which is processing data connection. + // For example:com.android.setupwizard.CAPTIVE_PORTAL + CONFIG_PROGRESS_ILLUSTRATION_CONNECTION( + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_CONNECTION, ResourceType.ILLUSTRATION), + + // The animation of loading screen used in those activities which is updating device. + // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE + CONFIG_PROGRESS_ILLUSTRATION_UPDATE( + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE, ResourceType.ILLUSTRATION), + + CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS( + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, ResourceType.INTEGER); public enum ResourceType { + INTEGER, BOOL, COLOR, DRAWABLE, STRING, DIMENSION, - FRACTION; + FRACTION, + ILLUSTRATION; } private final String resourceName; diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index eac403f..7b9f65b 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -18,7 +18,6 @@ package com.google.android.setupcompat.partnerconfig; import android.content.ContentResolver; import android.content.Context; -import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.drawable.Drawable; @@ -45,6 +44,9 @@ public class PartnerConfigHelper { public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner"; @VisibleForTesting public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig"; + + @VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig"; + private static PartnerConfigHelper instance = null; @VisibleForTesting Bundle resultBundle = null; @@ -92,16 +94,18 @@ public class PartnerConfigHelper { int result = 0; try { - String resourceName = resourceConfig.getResourceName(); - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { - result = resource.getColor(resourceEntry.getResourceId(), null); + result = resource.getColor(resId, null); } else { - result = resource.getColor(resourceEntry.getResourceId()); + result = resource.getColor(resId); } partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { + } catch (NullPointerException exception) { // fall through } return result; @@ -127,25 +131,25 @@ public class PartnerConfigHelper { Drawable result = null; try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); // for @null TypedValue outValue = new TypedValue(); - resource.getValue(resourceEntry.getResourceId(), outValue, true); + resource.getValue(resId, outValue, true); if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { return result; } if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - result = resource.getDrawable(resourceEntry.getResourceId(), null); + result = resource.getDrawable(resId, null); } else { - result = resource.getDrawable(resourceEntry.getResourceId()); + result = resource.getDrawable(resId); } partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException - | NullPointerException - | NotFoundException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -171,11 +175,14 @@ public class PartnerConfigHelper { String result = null; try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getString(resourceEntry.getResourceId()); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getString(resId); partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { + } catch (NullPointerException exception) { // fall through } return result; @@ -202,11 +209,14 @@ public class PartnerConfigHelper { boolean result = defaultValue; try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getBoolean(resourceEntry.getResourceId()); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getBoolean(resId); partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { + } catch (NullPointerException exception) { // fall through } return result; @@ -244,17 +254,18 @@ public class PartnerConfigHelper { float result = defaultValue; try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getDimension(resourceEntry.getResourceId()); - TypedValue value = - getTypedValueFromResource( - resource, resourceEntry.getResourceId(), TypedValue.TYPE_DIMENSION); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getDimension(resId); + TypedValue value = getTypedValueFromResource(resource, resId, TypedValue.TYPE_DIMENSION); partnerResourceCache.put(resourceConfig, value); result = getDimensionFromTypedValue( context, (TypedValue) partnerResourceCache.get(resourceConfig)); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { + } catch (NullPointerException exception) { // fall through } return result; @@ -291,16 +302,63 @@ public class PartnerConfigHelper { float result = defaultValue; try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getFraction(resourceEntry.getResourceId(), 1, 1); + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + result = resource.getFraction(resId, 1, 1); partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { + } catch (NullPointerException exception) { // fall through } return result; } + /** + * Returns the {@link ResourceEntry} of given {@code resourceConfig}, or {@code null} if the given + * {@code resourceConfig} is not found. If the {@link ResourceType} of the given {@code + * resourceConfig} is not illustration, IllegalArgumentException will be thrown. + * + * @param context The context of client activity + * @param resourceConfig The {@link PartnerConfig} of target resource + */ + @Nullable + public ResourceEntry getIllustrationResourceEntry( + @NonNull Context context, PartnerConfig resourceConfig) { + if (resourceConfig.getResourceType() != ResourceType.ILLUSTRATION) { + throw new IllegalArgumentException("Not a illustration resource"); + } + + if (partnerResourceCache.containsKey(resourceConfig)) { + return (ResourceEntry) partnerResourceCache.get(resourceConfig); + } + + try { + ResourceEntry resourceEntry = + getResourceEntryFromKey(context, resourceConfig.getResourceName()); + + Resources resource = resourceEntry.getResources(); + int resId = resourceEntry.getResourceId(); + + // TODO: The illustration resource entry validation should validate is it a video + // resource or not? + // for @null + TypedValue outValue = new TypedValue(); + resource.getValue(resId, outValue, true); + if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) { + return null; + } + + partnerResourceCache.put(resourceConfig, resourceEntry); + return resourceEntry; + } catch (NullPointerException exception) { + // fall through + } + + return null; + } + private void getPartnerConfigBundle(Context context) { if (resultBundle == null || resultBundle.isEmpty()) { try { @@ -316,29 +374,20 @@ public class PartnerConfigHelper { .call( contentUri, SUW_GET_PARTNER_CONFIG_METHOD, /* arg= */ null, /* extras= */ null); partnerResourceCache.clear(); - } catch (IllegalArgumentException exception) { + } catch (IllegalArgumentException | SecurityException exception) { Log.w(TAG, "Fail to get config from suw provider"); } } } - private Resources getResourcesByPackageName(Context context, String packageName) - throws PackageManager.NameNotFoundException { - PackageManager manager = context.getPackageManager(); - if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { - return manager.getResourcesForApplication( - manager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_COMPONENTS)); - } else { - return manager.getResourcesForApplication( - manager.getApplicationInfo(packageName, PackageManager.GET_DISABLED_COMPONENTS)); - } - } - - private ResourceEntry getResourceEntryFromKey(String resourceName) { - if (resultBundle == null) { - return null; + @Nullable + private ResourceEntry getResourceEntryFromKey(Context context, String resourceName) { + Bundle resourceEntryBundle = resultBundle.getBundle(resourceName); + Bundle fallbackBundle = resultBundle.getBundle(KEY_FALLBACK_CONFIG); + if (fallbackBundle != null) { + resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName)); } - return ResourceEntry.fromBundle(resultBundle.getBundle(resourceName)); + return ResourceEntry.fromBundle(context, resourceEntryBundle); } @VisibleForTesting diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java index 87f51ba..e5c5442 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java @@ -43,6 +43,8 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_FOOTER_BUTTON_RADIUS, PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA, PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, + 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_SECONDARY_BUTTON_BG_COLOR, @@ -57,6 +59,16 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_DESCRIPTION_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, + PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, + PartnerConfigKey.KEY_CONTENT_TEXT_COLOR, + PartnerConfigKey.KEY_CONTENT_LINK_TEXT_COLOR, + PartnerConfigKey.KEY_CONTENT_FONT_FAMILY, + PartnerConfigKey.KEY_CONTENT_LAYOUT_GRAVITY, + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DEFAULT, + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_ACCOUNT, + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_CONNECTION, + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE, + PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, }) // TODO: can be removed and always reference PartnerConfig.getResourceName()? @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @@ -121,6 +133,12 @@ public @interface PartnerConfigKey { // Text size of the footer button String KEY_FOOTER_BUTTON_TEXT_SIZE = "setup_compat_footer_button_text_size"; + // Disabled background alpha of the footer buttons + String KEY_FOOTER_BUTTON_DISABLED_ALPHA = "setup_compat_footer_button_disabled_alpha"; + + // Disabled background color of the footer buttons + String KEY_FOOTER_BUTTON_DISABLED_BG_COLOR = "setup_compat_footer_button_disabled_bg_color"; + // Background color of the primary footer button String KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR = "setup_compat_footer_primary_button_bg_color"; @@ -162,4 +180,38 @@ public @interface PartnerConfigKey { // Font family of the description String KEY_DESCRIPTION_FONT_FAMILY = "setup_design_description_font_family"; + + // Text size of the body content text + String KEY_CONTENT_TEXT_SIZE = "setup_design_content_text_size"; + + // Text color of the body content text + String KEY_CONTENT_TEXT_COLOR = "setup_design_content_text_color"; + + // Link text color of the body content text + String KEY_CONTENT_LINK_TEXT_COLOR = "setup_design_content_link_text_color"; + + // Font family of the body content text + String KEY_CONTENT_FONT_FAMILY = "setup_design_content_font_family"; + + // Gravity of the body content text + String KEY_CONTENT_LAYOUT_GRAVITY = "setup_design_content_layout_gravity"; + + // The animation of loading screen used in those activities which is non of below type. + String KEY_PROGRESS_ILLUSTRATION_DEFAULT = "progress_illustration_custom_default"; + + // The animation of loading screen used in those activity which is processing account info or + // related functions. + // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT + String KEY_PROGRESS_ILLUSTRATION_ACCOUNT = "progress_illustration_custom_account"; + + // The animation of loading screen used in those activity which is processing data connection. + // For example:com.android.setupwizard.CAPTIVE_PORTAL + String KEY_PROGRESS_ILLUSTRATION_CONNECTION = "progress_illustration_custom_connection"; + + // The animation of loading screen used in those activities which is updating device. + // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE + String KEY_PROGRESS_ILLUSTRATION_UPDATE = "progress_illustration_custom_update"; + + // The minimum illustration display time, set to 0 may cause the illustration stuck + String KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS = "progress_illustration_display_minimum_ms"; } diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java index 2794f22..8f7c9d8 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java @@ -16,15 +16,29 @@ package com.google.android.setupcompat.partnerconfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import android.util.Log; /** * A potentially cross-package resource entry, which can then be retrieved using {@link - * PackageManager#getApplicationForResources}. This class can also be sent across to other packages + * PackageManager#getResourcesForApplication}. This class can also be sent across to other packages * on IPC via the Bundle representation. */ public final class ResourceEntry { + + private static final String TAG = ResourceEntry.class.getSimpleName(); + + @VisibleForTesting static final String KEY_FALLBACK_CONFIG = "fallbackConfig"; + @VisibleForTesting static final String KEY_PACKAGE_NAME = "packageName"; @VisibleForTesting static final String KEY_RESOURCE_NAME = "resourceName"; @VisibleForTesting static final String KEY_RESOURCE_ID = "resourceId"; @@ -34,11 +48,22 @@ public final class ResourceEntry { private final int resourceId; /** - * Creates a {@code ResourceEntry} object from a provided bundle. + * The {@link Resources} for accessing a specific package's resources. This is {@code null} only + * if the deprecated constructor {@link #ResourceEntry(String, String, int)} is used. + */ + @Nullable private final Resources resources; + + /** + * Creates a {@code ResourceEntry} object from a provided bundle or the fallback resource if + * partner resource not found and the {@code fallbackConfig} key exists in provided bundle. + * Returns {@code null} if fallback package is not found or the {@code bundle} doesn't contain + * packageName, resourceName, or resourceId. * + * @param context the context need to retrieve the {@link Resources} * @param bundle the source bundle needs to have all the information for a {@code ResourceEntry} */ - public static ResourceEntry fromBundle(Bundle bundle) { + @Nullable + public static ResourceEntry fromBundle(@NonNull Context context, Bundle bundle) { String packageName; String resourceName; int resourceId; @@ -50,13 +75,31 @@ public final class ResourceEntry { packageName = bundle.getString(KEY_PACKAGE_NAME); resourceName = bundle.getString(KEY_RESOURCE_NAME); resourceId = bundle.getInt(KEY_RESOURCE_ID); - return new ResourceEntry(packageName, resourceName, resourceId); + try { + return new ResourceEntry( + packageName, resourceName, resourceId, getResourcesByPackageName(context, packageName)); + } catch (NameNotFoundException e) { + Bundle fallbackBundle = bundle.getBundle(KEY_FALLBACK_CONFIG); + if (fallbackBundle != null) { + Log.w(TAG, packageName + " not found, " + resourceName + " fallback to default value"); + return fromBundle(context, fallbackBundle); + } + } + return null; } + /** @deprecated Use {@link #ResourceEntry(String, String, int, Resources)} instead. */ + @Deprecated public ResourceEntry(String packageName, String resourceName, int resourceId) { + this(packageName, resourceName, resourceId, /* resources= */ null); + } + + public ResourceEntry( + String packageName, String resourceName, int resourceId, @Nullable Resources resources) { this.packageName = packageName; this.resourceName = resourceName; this.resourceId = resourceId; + this.resources = resources; } public String getPackageName() { @@ -72,9 +115,17 @@ public final class ResourceEntry { } /** + * Returns a {@link Resources} for accessing specific package's resources. It will be {@code null} + * when the {@link #ResourceEntry(String, String, int)} is used). + */ + public Resources getResources() { + return resources; + } + + /** * Returns a bundle representation of this resource entry, which can then be sent over IPC. * - * @see #fromBundle(Bundle) + * @see #fromBundle(Context, Bundle) */ public Bundle toBundle() { Bundle result = new Bundle(); @@ -83,4 +134,16 @@ public final class ResourceEntry { result.putInt(KEY_RESOURCE_ID, resourceId); return result; } + + private static Resources getResourcesByPackageName(Context context, String packageName) + throws NameNotFoundException { + PackageManager manager = context.getPackageManager(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { + return manager.getResourcesForApplication( + manager.getApplicationInfo(packageName, PackageManager.MATCH_DISABLED_COMPONENTS)); + } else { + return manager.getResourcesForApplication( + manager.getApplicationInfo(packageName, PackageManager.GET_DISABLED_COMPONENTS)); + } + } } |