diff options
author | Setup Wizard Team <android-setup-team-eng@google.com> | 2019-03-14 10:00:10 +0800 |
---|---|---|
committer | pastychang <pastychang@google.com> | 2019-03-15 10:37:13 +0800 |
commit | 1ed3073417396ad4d3bec9d6a5722b490a49e47e (patch) | |
tree | 6be9a1c1b6f1d6bbba588ccc23cbd069bad56622 /main/java/com/google/android/setupcompat | |
parent | b5345f3d3b611fcf3513c613d1663f424414a12f (diff) | |
download | setupcompat-1ed3073417396ad4d3bec9d6a5722b490a49e47e.tar.gz |
Import updated Android SetupCompat Library 238357591
Test: mm
PiperOrigin-RevId: 238357591
Change-Id: Ifa32c7db8d5383f076582aeb7b44f42499a1217a
Diffstat (limited to 'main/java/com/google/android/setupcompat')
14 files changed, 348 insertions, 973 deletions
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index cef0aee..faeda2c 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -21,9 +21,10 @@ import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.content.res.TypedArray; -import android.os.Build; +import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; +import androidx.annotation.ColorInt; import androidx.annotation.LayoutRes; import android.util.AttributeSet; import android.util.Log; @@ -38,6 +39,8 @@ import com.google.android.setupcompat.lifecycle.LifecycleFragment; 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.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.StatusBarMixin; @@ -49,7 +52,9 @@ public class PartnerCustomizationLayout extends TemplateLayout { // Log tags can have at most 23 characters on N or before. private static final String TAG = "PartnerCustomizedLayout"; - private final boolean suwVersionSupportPartnerResource = Build.VERSION.SDK_INT > VERSION_CODES.P; + private final boolean suwVersionSupportPartnerResource = isAtLeastQ(); + + private boolean applyPartnerResource; private Activity activity; public PartnerCustomizationLayout(Context context) { @@ -93,7 +98,7 @@ public class PartnerCustomizationLayout extends TemplateLayout { a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true); a.recycle(); - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) { setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } @@ -109,13 +114,10 @@ public class PartnerCustomizationLayout extends TemplateLayout { + usePartnerResource); if (suwVersionSupportPartnerResource && isSetupFlow && !applyPartnerResource()) { - Log.w( - TAG, - "This is inavlid usage that applyPartnerResource() should returns true" - + " during setup wizard flow"); + Log.w(TAG, "applyPartnerResource() should return true during setup wizard flow"); } - boolean applyPartnerResource = + applyPartnerResource = suwVersionSupportPartnerResource && applyPartnerResource() && (isSetupFlow || usePartnerResource); @@ -124,17 +126,22 @@ public class PartnerCustomizationLayout extends TemplateLayout { new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr, applyPartnerResource)); registerMixin( SystemNavBarMixin.class, - new SystemNavBarMixin( - this, activity.getWindow(), attrs, defStyleAttr, applyPartnerResource)); + new SystemNavBarMixin(this, activity.getWindow(), applyPartnerResource)); registerMixin( FooterBarMixin.class, new FooterBarMixin(this, attrs, defStyleAttr, applyPartnerResource)); + getMixin(SystemNavBarMixin.class).applyPartnerCustomizations(attrs, defStyleAttr); + // Override the FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_TRANSLUCENT_STATUS, // FLAG_TRANSLUCENT_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attributes of window forces // showing status bar and navigation bar. activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + if (applyPartnerResource) { + updateContentBackgroundColorWithPartnerConfig(); + } } @Override @@ -200,6 +207,14 @@ public class PartnerCustomizationLayout extends TemplateLayout { } } + // TODO(b/127925696): remove the code for pre-release version of Android Q + private static boolean isAtLeastQ() { + return (VERSION.SDK_INT > VERSION_CODES.P) + || (VERSION.CODENAME.length() == 1 + && VERSION.CODENAME.charAt(0) >= 'Q' + && VERSION.CODENAME.charAt(0) <= 'Z'); + } + /** * This method determines applying the partner resource regardless inside setup wizard flow or * not. It always returns true indicating applying partner resource inside setup wizard flow and @@ -222,4 +237,18 @@ public class PartnerCustomizationLayout extends TemplateLayout { footerStub.setLayoutResource(footer); return footerStub.inflate(); } + + /** Returns if the current layout/activity applies partner customized configurations or not. */ + protected boolean shouldApplyPartnerResource() { + return applyPartnerResource; + } + + /** Updates the background color of this layout with the partner-customizable background color. */ + private void updateContentBackgroundColorWithPartnerConfig() { + @ColorInt + int color = + PartnerConfigHelper.get(getContext()) + .getColor(getContext(), PartnerConfig.CONFIG_LAYOUT_BACKGROUND_COLOR); + this.getRootView().setBackgroundColor(color); + } } diff --git a/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java b/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java index a617fd7..71aa898 100644 --- a/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java +++ b/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java @@ -16,6 +16,8 @@ package com.google.android.setupcompat.lifecycle; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; @@ -94,7 +96,7 @@ public class LifecycleFragment extends Fragment { @Override public void onDetach() { super.onDetach(); - SetupMetricsLogger.logDuration(getContext(), metricKey, durationInNanos); + SetupMetricsLogger.logDuration(getContext(), metricKey, NANOSECONDS.toMillis(durationInNanos)); } @Override diff --git a/main/java/com/google/android/setupcompat/logging/internal/FooterBarMixinMetrics.java b/main/java/com/google/android/setupcompat/logging/internal/FooterBarMixinMetrics.java index 8886f51..007aff9 100644 --- a/main/java/com/google/android/setupcompat/logging/internal/FooterBarMixinMetrics.java +++ b/main/java/com/google/android/setupcompat/logging/internal/FooterBarMixinMetrics.java @@ -18,6 +18,8 @@ package com.google.android.setupcompat.logging.internal; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import androidx.annotation.StringDef; import androidx.annotation.VisibleForTesting; @@ -44,9 +46,9 @@ public class FooterBarMixinMetrics { @VisibleForTesting public @interface FooterButtonVisibility { String UNKNOWN = "Unknown"; - String VISIBLE_USING_XML = "VisibileUsingXml"; + String VISIBLE_USING_XML = "VisibleUsingXml"; String VISIBLE = "Visible"; - String VISIBLE_USING_XML_TO_INVISIBLE = "VisibileUsingXml_to_Invisible"; + String VISIBLE_USING_XML_TO_INVISIBLE = "VisibleUsingXml_to_Invisible"; String VISIBLE_TO_INVISIBLE = "Visible_to_Invisible"; String INVISIBLE_TO_VISIBLE = "Invisible_to_Visible"; String INVISIBLE = "Invisible"; @@ -92,35 +94,36 @@ public class FooterBarMixinMetrics { /** Saves footer button visibility when finish state */ public void updateButtonVisibility( - boolean isPrimaryButtonVisiable, boolean isSecondaryButtonVisible) { + boolean isPrimaryButtonVisible, boolean isSecondaryButtonVisible) { primaryButtonVisibility = - updateButtonVisibilityState(primaryButtonVisibility, isPrimaryButtonVisiable); + updateButtonVisibilityState(primaryButtonVisibility, isPrimaryButtonVisible); secondaryButtonVisibility = updateButtonVisibilityState(secondaryButtonVisibility, isSecondaryButtonVisible); } @FooterButtonVisibility static String updateButtonVisibilityState( - @FooterButtonVisibility String origionalVisibility, boolean isVisible) { - if (!origionalVisibility.equals(FooterButtonVisibility.VISIBLE_USING_XML) - && !origionalVisibility.equals(FooterButtonVisibility.VISIBLE) - && !origionalVisibility.equals(FooterButtonVisibility.INVISIBLE)) { - throw new IllegalStateException("Illegal visibility state:" + origionalVisibility); + @FooterButtonVisibility String originalVisibility, boolean isVisible) { + if (!FooterButtonVisibility.VISIBLE_USING_XML.equals(originalVisibility) + && !FooterButtonVisibility.VISIBLE.equals(originalVisibility) + && !FooterButtonVisibility.INVISIBLE.equals(originalVisibility)) { + throw new IllegalStateException("Illegal visibility state: " + originalVisibility); } - if (isVisible && origionalVisibility.equals(FooterButtonVisibility.INVISIBLE)) { + if (isVisible && FooterButtonVisibility.INVISIBLE.equals(originalVisibility)) { return FooterButtonVisibility.INVISIBLE_TO_VISIBLE; } else if (!isVisible) { - if (origionalVisibility.equals(FooterButtonVisibility.VISIBLE_USING_XML)) { + if (FooterButtonVisibility.VISIBLE_USING_XML.equals(originalVisibility)) { return FooterButtonVisibility.VISIBLE_USING_XML_TO_INVISIBLE; - } else if (origionalVisibility.equals(FooterButtonVisibility.VISIBLE)) { + } else if (FooterButtonVisibility.VISIBLE.equals(originalVisibility)) { return FooterButtonVisibility.VISIBLE_TO_INVISIBLE; } } - return origionalVisibility; + return originalVisibility; } /** Returns metrics data for logging */ + @TargetApi(VERSION_CODES.Q) public PersistableBundle getMetrics() { PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putString(EXTRA_PRIMARY_BUTTON_VISIBILITY, primaryButtonVisibility); diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 25fe418..4284ac9 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 android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -52,6 +53,8 @@ import android.widget.LinearLayout.LayoutParams; import com.google.android.setupcompat.R; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.logging.internal.FooterBarMixinMetrics; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.template.FooterButton.ButtonType; import java.util.concurrent.atomic.AtomicInteger; @@ -89,7 +92,6 @@ public class FooterBarMixin implements Mixin { return new FooterButton.OnButtonEventListener() { - @Override public void onEnabledChanged(boolean enabled) { if (buttonContainer != null) { @@ -196,7 +198,14 @@ public class FooterBarMixin implements Mixin { } else { buttonContainer.setId(generateViewId()); } - updateBottomBarPadding(); + updateFooterBarPadding(); + if (applyPartnerResources) { + @ColorInt + int color = + PartnerConfigHelper.get(context) + .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR); + buttonContainer.setBackgroundColor(color); + } } return buttonContainer; } @@ -416,7 +425,7 @@ public class FooterBarMixin implements Mixin { private void updateButtonAttrsWithPartnerConfig( Button button, boolean isPrimaryButton, @ButtonType int buttonType) { updateButtonTextColorWithPartnerConfig(button, isPrimaryButton); - updateButtonTextSizeWithPartnerConfig(button, isPrimaryButton); + updateButtonTextSizeWithPartnerConfig(button); updateButtonTypeFaceWithPartnerConfig(button); updateButtonBackgroundWithPartnerConfig(button, isPrimaryButton); updateButtonRadiusWithPartnerConfig(button); @@ -438,18 +447,13 @@ public class FooterBarMixin implements Mixin { button.setTextColor(color); } - private void updateButtonTextSizeWithPartnerConfig(Button button, boolean isPrimaryButton) { - float size; - if (isPrimaryButton) { - size = - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_SIZE); - } else { - size = - PartnerConfigHelper.get(context) - .getDimension(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_SIZE); + private void updateButtonTextSizeWithPartnerConfig(Button button) { + float size = + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE); + if (size > 0) { + button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } - button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } private void updateButtonTypeFaceWithPartnerConfig(Button button) { @@ -639,7 +643,7 @@ public class FooterBarMixin implements Mixin { return footerStub.inflate(); } - private void updateBottomBarPadding() { + private void updateFooterBarPadding() { if (buttonContainer == null) { // Ignore action since buttonContainer is null return; @@ -692,6 +696,7 @@ public class FooterBarMixin implements Mixin { /** * Assigns logging metrics to bundle for PartnerCustomizationLayout to log metrics to SetupWizard. */ + @TargetApi(VERSION_CODES.Q) public PersistableBundle getLoggingMetrics() { return metrics.getMetrics(); } diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java index f913c0b..e2ef6c6 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterButton.java @@ -18,8 +18,10 @@ package com.google.android.setupcompat.template; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -320,6 +322,7 @@ public final class FooterButton implements OnClickListener { * Returns footer button related metrics bundle for PartnerCustomizationLayout to log to * SetupWizard. */ + @TargetApi(VERSION_CODES.Q) public PersistableBundle getMetrics(String buttonName) { PersistableBundle bundle = new PersistableBundle(); bundle.putString(buttonName + KEY_BUTTON_TEXT, getText().toString()); diff --git a/main/java/com/google/android/setupcompat/template/HeaderMixin.java b/main/java/com/google/android/setupcompat/template/HeaderMixin.java deleted file mode 100644 index f6ae697..0000000 --- a/main/java/com/google/android/setupcompat/template/HeaderMixin.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.setupcompat.template; - -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.widget.TextView; -import com.google.android.setupcompat.R; -import com.google.android.setupcompat.internal.TemplateLayout; - -/** - * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the header text. - */ -public class HeaderMixin implements Mixin { - - private final TemplateLayout templateLayout; - - /** - * @param layout The layout this Mixin belongs to. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public HeaderMixin( - @NonNull TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { - templateLayout = layout; - - final TypedArray a = - layout - .getContext() - .obtainStyledAttributes(attrs, R.styleable.SucHeaderMixin, defStyleAttr, 0); - - // Set the header text - final CharSequence headerText = a.getText(R.styleable.SucHeaderMixin_sucHeaderText); - if (headerText != null) { - setText(headerText); - } - // Set the header text color - final ColorStateList headerTextColor = - a.getColorStateList(R.styleable.SucHeaderMixin_sucHeaderTextColor); - if (headerTextColor != null) { - setTextColor(headerTextColor); - } - - a.recycle(); - } - - /** @return The TextView displaying the header. */ - public TextView getTextView() { - return (TextView) templateLayout.findManagedViewById(R.id.suc_layout_title); - } - - /** - * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}. - * - * @param title The resource ID of the text to be set as header. - */ - public void setText(int title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } - } - - /** - * Sets the header text. This can also be set via the XML attribute {@code app:sucHeaderText}. - * - * @param title The text to be set as header. - */ - public void setText(CharSequence title) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setText(title); - } - } - - /** @return The current header text. */ - public CharSequence getText() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getText() : null; - } - - /** - * Sets the color of the header text. This can also be set via XML using {@code - * app:sucHeaderTextColor}. - * - * @param color The text color of the header. - */ - public void setTextColor(ColorStateList color) { - final TextView titleView = getTextView(); - if (titleView != null) { - titleView.setTextColor(color); - } - } - - /** Returns the current text color of the header. */ - public ColorStateList getTextColor() { - final TextView titleView = getTextView(); - return titleView != null ? titleView.getTextColors() : null; - } -} diff --git a/main/java/com/google/android/setupcompat/template/IconMixin.java b/main/java/com/google/android/setupcompat/template/IconMixin.java deleted file mode 100644 index c2002e2..0000000 --- a/main/java/com/google/android/setupcompat/template/IconMixin.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.setupcompat.template; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import androidx.annotation.DrawableRes; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import com.google.android.setupcompat.R; -import com.google.android.setupcompat.internal.TemplateLayout; - -/** - * A {@link com.google.android.setupcompat.template.Mixin} for setting an icon on the template - * layout. - */ -public class IconMixin implements Mixin { - - private final TemplateLayout templateLayout; - - /** - * @param layout The template layout that this Mixin is a part of. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - */ - public IconMixin(TemplateLayout layout, AttributeSet attrs, int defStyleAttr) { - templateLayout = layout; - final Context context = layout.getContext(); - - final TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.SucIconMixin, defStyleAttr, 0); - - final @DrawableRes int icon = a.getResourceId(R.styleable.SucIconMixin_android_icon, 0); - if (icon != 0) { - setIcon(icon); - } - - a.recycle(); - } - - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon. - */ - public void setIcon(Drawable icon) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE); - } - } - - /** - * Sets the icon on this layout. The icon can also be set in XML using {@code android:icon}. - * - * @param icon A drawable icon resource. - */ - public void setIcon(@DrawableRes int icon) { - final ImageView iconView = getView(); - if (iconView != null) { - // Note: setImageResource on the ImageView is overridden in AppCompatImageView for - // support lib users, which enables vector drawable compat to work on versions pre-L. - iconView.setImageResource(icon); - iconView.setVisibility(icon != 0 ? View.VISIBLE : View.GONE); - } - } - - /** @return The icon previously set in {@link #setIcon(Drawable)} or {@code android:icon} */ - public Drawable getIcon() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getDrawable() : null; - } - - /** Sets the content description of the icon view */ - public void setContentDescription(CharSequence description) { - final ImageView iconView = getView(); - if (iconView != null) { - iconView.setContentDescription(description); - } - } - - /** @return The content description of the icon view */ - public CharSequence getContentDescription() { - final ImageView iconView = getView(); - return iconView != null ? iconView.getContentDescription() : null; - } - - /** @return The ImageView responsible for displaying the icon. */ - protected ImageView getView() { - return (ImageView) templateLayout.findManagedViewById(R.id.suc_layout_icon); - } -} diff --git a/main/java/com/google/android/setupcompat/template/PartnerConfig.java b/main/java/com/google/android/setupcompat/template/PartnerConfig.java deleted file mode 100644 index 00a36b7..0000000 --- a/main/java/com/google/android/setupcompat/template/PartnerConfig.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 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.template; - -// TODO(b/121371322): optimize the enum -/** Resources that can be customized by partner overlay APK. */ -enum PartnerConfig { - - // Status bar background color or illustration. - CONFIG_STATUS_BAR_BACKGROUND(PartnerConfigKey.KEY_STATUS_BAR_BACKGROUND, ResourceType.DRAWABLE), - - // The same as "WindowLightStatusBar". If set true, the status bar icons will be drawn such - // that it is compatible with a light status bar background - CONFIG_LIGHT_STATUS_BAR(PartnerConfigKey.KEY_LIGHT_STATUS_BAR, ResourceType.BOOL), - - // Navigation bar background color - CONFIG_NAVIGATION_BAR_BG_COLOR(PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR, ResourceType.COLOR), - - // 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), - - // 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. - CONFIG_FOOTER_BUTTON_FONT_FAMILY( - PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY, ResourceType.STRING), - - // The icon for "add another" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER, ResourceType.DRAWABLE), - - // The icon for "cancel" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_CANCEL( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, ResourceType.DRAWABLE), - - // The icon for "clear" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_CLEAR( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CLEAR, ResourceType.DRAWABLE), - - // The icon for "done" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_DONE( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_DONE, ResourceType.DRAWABLE), - - // The icon for "next" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_NEXT( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_NEXT, ResourceType.DRAWABLE), - - // The icon for "opt-in" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_OPT_IN( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_OPT_IN, ResourceType.DRAWABLE), - - // The icon for "skip" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_SKIP( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_SKIP, ResourceType.DRAWABLE), - - // The icon for "stop" action. Can be "@null" for no icon. - CONFIG_FOOTER_BUTTON_ICON_STOP( - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_STOP, ResourceType.DRAWABLE), - - // Top padding of the footer buttons - CONFIG_FOOTER_BUTTON_PADDING_TOP( - PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_TOP, ResourceType.DIMENSION), - - // Bottom padding of the footer buttons - CONFIG_FOOTER_BUTTON_PADDING_BOTTOM( - PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_BOTTOM, ResourceType.DIMENSION), - - // Corner radius of the footer buttons - CONFIG_FOOTER_BUTTON_RADIUS(PartnerConfigKey.KEY_FOOTER_BUTTON_RADIUS, ResourceType.DIMENSION), - - // Ripple color alpha the footer buttons - CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA( - PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA, ResourceType.FRACTION), - - // Background color of the primary footer button - CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR( - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR), - - // Text color of the primary footer button - CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR( - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), - - // Text size of the primary footer button - CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_SIZE( - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_SIZE, ResourceType.DIMENSION), - - // Background color of the secondary footer button - CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR( - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, ResourceType.COLOR), - - // Text color of the secondary footer button - CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR( - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), - - // Text size of the secondary footer button - CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_SIZE( - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_SIZE, ResourceType.DIMENSION); - - public enum ResourceType { - BOOL, - COLOR, - DRAWABLE, - STRING, - DIMENSION, - FRACTION; - } - - private final String resourceName; - private final ResourceType resourceType; - - public ResourceType getResourceType() { - return resourceType; - } - - public String getResourceName() { - return resourceName; - } - - PartnerConfig(@PartnerConfigKey String resourceName, ResourceType type) { - this.resourceName = resourceName; - this.resourceType = type; - } -} diff --git a/main/java/com/google/android/setupcompat/template/PartnerConfigHelper.java b/main/java/com/google/android/setupcompat/template/PartnerConfigHelper.java deleted file mode 100644 index 9c540dd..0000000 --- a/main/java/com/google/android/setupcompat/template/PartnerConfigHelper.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 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.template; - -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; -import android.net.Uri; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import android.util.Log; -import android.util.TypedValue; -import com.google.android.setupcompat.template.PartnerConfig.ResourceType; -import com.google.android.setupcompat.util.ResourceEntry; -import java.util.EnumMap; - -/** The helper reads and caches the partner configurations from SUW. */ -@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public class PartnerConfigHelper { - - private static final String TAG = PartnerConfigHelper.class.getSimpleName(); - - @VisibleForTesting - public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner"; - - @VisibleForTesting public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig"; - private static PartnerConfigHelper instance = null; - - @VisibleForTesting Bundle resultBundle = null; - - @VisibleForTesting - final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class); - - public static synchronized PartnerConfigHelper get(@NonNull Context context) { - if (instance == null) { - instance = new PartnerConfigHelper(context); - } - return instance; - } - - private PartnerConfigHelper(Context context) { - getPartnerConfigBundle(context); - } - - /** - * Returns the color of given {@code resourceConfig}, or 0 if the given {@code resourceConfig} is - * not found. If the {@code ResourceType} of the given {@code resourceConfig} is not color, - * IllegalArgumentException will be thrown. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - */ - @ColorInt - public int getColor(@NonNull Context context, PartnerConfig resourceConfig) { - if (resourceConfig.getResourceType() != ResourceType.COLOR) { - throw new IllegalArgumentException("Not a color resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (int) partnerResourceCache.get(resourceConfig); - } - - int result = 0; - try { - String resourceName = resourceConfig.getResourceName(); - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { - result = resource.getColor(resourceEntry.getResourceId(), null); - } else { - result = resource.getColor(resourceEntry.getResourceId()); - } - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { - // fall through - } - return result; - } - - /** - * Returns the {@code Drawable} of given {@code resourceConfig}, or {@code null} if the given - * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code - * resourceConfig} is not drawable, IllegalArgumentException will be thrown. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - */ - @Nullable - public Drawable getDrawable(@NonNull Context context, PartnerConfig resourceConfig) { - if (resourceConfig.getResourceType() != ResourceType.DRAWABLE) { - throw new IllegalArgumentException("Not a drawable resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (Drawable) partnerResourceCache.get(resourceConfig); - } - - Drawable result = null; - try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - - // for @null - TypedValue outValue = new TypedValue(); - resource.getValue(resourceEntry.getResourceId(), 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); - } else { - result = resource.getDrawable(resourceEntry.getResourceId()); - } - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException - | NullPointerException - | NotFoundException exception) { - // fall through - } - return result; - } - - /** - * Returns the string of the given {@code resourceConfig}, or {@code null} if the given {@code - * resourceConfig} is not found. If the {@code ResourceType} of the given {@code resourceConfig} - * is not string, IllegalArgumentException will be thrown. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - */ - @Nullable - public String getString(@NonNull Context context, PartnerConfig resourceConfig) { - if (resourceConfig.getResourceType() != ResourceType.STRING) { - throw new IllegalArgumentException("Not a string resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (String) partnerResourceCache.get(resourceConfig); - } - - String result = null; - try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getString(resourceEntry.getResourceId()); - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { - // fall through - } - return result; - } - - /** - * Returns the boolean of given {@code resourceConfig}, or {@code defaultValue} if the given - * {@code resourceName} is not found. If the {@code ResourceType} of the given {@code - * resourceConfig} is not boolean, IllegalArgumentException will be thrown. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - * @param defaultValue The default value - */ - public boolean getBoolean( - @NonNull Context context, PartnerConfig resourceConfig, boolean defaultValue) { - if (resourceConfig.getResourceType() != ResourceType.BOOL) { - throw new IllegalArgumentException("Not a bool resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (boolean) partnerResourceCache.get(resourceConfig); - } - - boolean result = defaultValue; - try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getBoolean(resourceEntry.getResourceId()); - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { - // fall through - } - return result; - } - - /** - * Returns the dimension of given {@code resourceConfig}. The default return value is 0. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - */ - public float getDimension(@NonNull Context context, PartnerConfig resourceConfig) { - return getDimension(context, resourceConfig, 0); - } - - /** - * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} not - * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code - * resourceConfig} is not dimension, will throw IllegalArgumentException. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - * @param defaultValue The default value - */ - public float getDimension( - @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { - if (resourceConfig.getResourceType() != ResourceType.DIMENSION) { - throw new IllegalArgumentException("Not a dimension resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (float) partnerResourceCache.get(resourceConfig); - } - - float result = defaultValue; - try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getDimension(resourceEntry.getResourceId()); - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { - // fall through - } - return result; - } - - /** - * Returns the float of given {@code resourceConfig}. The default return value is 0. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - */ - public float getFraction(@NonNull Context context, PartnerConfig resourceConfig) { - return getFraction(context, resourceConfig, 0.0f); - } - - /** - * Returns the float of given {@code resourceConfig}. If the given {@code resourceConfig} not - * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code - * resourceConfig} is not fraction, will throw IllegalArgumentException. - * - * @param context The context of client activity - * @param resourceConfig The {@code PartnerConfig} of target resource - * @param defaultValue The default value - */ - public float getFraction( - @NonNull Context context, PartnerConfig resourceConfig, float defaultValue) { - if (resourceConfig.getResourceType() != ResourceType.FRACTION) { - throw new IllegalArgumentException("Not a fraction resource"); - } - - if (partnerResourceCache.containsKey(resourceConfig)) { - return (float) partnerResourceCache.get(resourceConfig); - } - - float result = defaultValue; - try { - ResourceEntry resourceEntry = getResourceEntryFromKey(resourceConfig.getResourceName()); - Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName()); - result = resource.getFraction(resourceEntry.getResourceId(), 1, 1); - partnerResourceCache.put(resourceConfig, result); - } catch (PackageManager.NameNotFoundException | NullPointerException exception) { - // fall through - } - return result; - } - - private void getPartnerConfigBundle(Context context) { - if (resultBundle == null || resultBundle.isEmpty()) { - try { - Uri contentUri = - new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(SUW_AUTHORITY) - .appendPath(SUW_GET_PARTNER_CONFIG_METHOD) - .build(); - resultBundle = - context - .getContentResolver() - .call( - contentUri, SUW_GET_PARTNER_CONFIG_METHOD, /* arg= */ null, /* extras= */ null); - partnerResourceCache.clear(); - } catch (IllegalArgumentException 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(); - return manager.getResourcesForApplication(packageName); - } - - private ResourceEntry getResourceEntryFromKey(String resourceName) { - if (resultBundle == null) { - return null; - } - return ResourceEntry.fromBundle(resultBundle.getBundle(resourceName)); - } - - @VisibleForTesting - public static synchronized void resetForTesting() { - instance = null; - } -} diff --git a/main/java/com/google/android/setupcompat/template/PartnerConfigKey.java b/main/java/com/google/android/setupcompat/template/PartnerConfigKey.java deleted file mode 100644 index d79c152..0000000 --- a/main/java/com/google/android/setupcompat/template/PartnerConfigKey.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 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.template; - -import androidx.annotation.StringDef; -import androidx.annotation.VisibleForTesting; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Resource names that can be customized by partner overlay APK. */ -@Retention(RetentionPolicy.SOURCE) -@StringDef({ - PartnerConfigKey.KEY_STATUS_BAR_BACKGROUND, - PartnerConfigKey.KEY_LIGHT_STATUS_BAR, - PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR, - PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR, - PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CLEAR, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_DONE, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_NEXT, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_OPT_IN, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_SKIP, - PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_STOP, - PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_TOP, - PartnerConfigKey.KEY_FOOTER_BUTTON_PADDING_BOTTOM, - PartnerConfigKey.KEY_FOOTER_BUTTON_RADIUS, - PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA, - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, - PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_SIZE, - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, - PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_SIZE, -}) -// TODO(121371322): can be removed and always reference PartnerConfig.getResourceName()? -@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public @interface PartnerConfigKey { - // Status bar background color or illustration. - String KEY_STATUS_BAR_BACKGROUND = "setup_compat_status_bar_background"; - - // The same as "WindowLightStatusBar". If set true, the status bar icons will be drawn such - // that it is compatible with a light status bar background - String KEY_LIGHT_STATUS_BAR = "setup_compat_light_status_bar"; - - // Navigation bar background color - String KEY_NAVIGATION_BAR_BG_COLOR = "setup_compat_navigation_bar_bg_color"; - - // 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. - String KEY_LIGHT_NAVIGATION_BAR = "setup_compat_light_navigation_bar"; - - // 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"; - - // The icon for "add another" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER = "setup_compat_footer_button_icon_add_another"; - - // The icon for "cancel" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_CANCEL = "setup_compat_footer_button_icon_cancel"; - - // The icon for "clear" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_CLEAR = "setup_compat_footer_button_icon_clear"; - - // The icon for "done" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_DONE = "setup_compat_footer_button_icon_done"; - - // The icon for "next" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_NEXT = "setup_compat_footer_button_icon_next"; - - // The icon for "opt-in" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_OPT_IN = "setup_compat_footer_button_icon_opt_in"; - - // The icon for "skip" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_SKIP = "setup_compat_footer_button_icon_skip"; - - // The icon for "stop" action. Can be "@null" for no icon. - String KEY_FOOTER_BUTTON_ICON_STOP = "setup_compat_footer_button_icon_stop"; - - // Top padding of the footer buttons - String KEY_FOOTER_BUTTON_PADDING_TOP = "setup_compat_footer_button_padding_top"; - - // Bottom padding of the footer buttons - String KEY_FOOTER_BUTTON_PADDING_BOTTOM = "setup_compat_footer_button_padding_bottom"; - - // Corner radius of the footer buttons - String KEY_FOOTER_BUTTON_RADIUS = "setup_compat_footer_button_radius"; - - // Ripple color alpha of the footer button - String KEY_FOOTER_BUTTON_RIPPLE_ALPHA = "setup_compat_footer_button_ripple_alpha"; - - // Background color of the primary footer button - String KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR = "setup_compat_footer_primary_button_bg_color"; - - // Text color of the primary footer button - String KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR = "setup_compat_footer_primary_button_text_color"; - - // Text size of the primary footer button - String KEY_FOOTER_PRIMARY_BUTTON_TEXT_SIZE = "setup_compat_footer_primary_button_text_size"; - - // 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"; - - // Text size of the secondary footer button - String KEY_FOOTER_SECONDARY_BUTTON_TEXT_SIZE = "setup_compat_footer_secondary_button_text_size"; -} diff --git a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java index 38e903b..2aa37dc 100644 --- a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java @@ -35,6 +35,8 @@ import android.view.Window; import android.widget.LinearLayout; import com.google.android.setupcompat.PartnerCustomizationLayout; import com.google.android.setupcompat.R; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; import com.google.android.setupcompat.view.StatusBarBackgroundLayout; /** diff --git a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java index f137e7f..eae876c 100644 --- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; +import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import androidx.annotation.AttrRes; import androidx.annotation.NonNull; @@ -32,6 +33,10 @@ import android.view.View; import android.view.Window; import com.google.android.setupcompat.PartnerCustomizationLayout; import com.google.android.setupcompat.R; +import com.google.android.setupcompat.internal.TemplateLayout; +import com.google.android.setupcompat.partnerconfig.PartnerConfig; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.util.SystemBarBaseHelper; /** * A {@link Mixin} for setting and getting background color and window compatible with light theme @@ -39,38 +44,39 @@ import com.google.android.setupcompat.R; */ public class SystemNavBarMixin implements Mixin { - private final PartnerCustomizationLayout partnerCustomizationLayout; - private final Window windowOfActivity; - private final View decorView; + private final TemplateLayout templateLayout; + @Nullable private final Window windowOfActivity; @VisibleForTesting final boolean applyPartnerResources; + private int sucSystemNavBarBackgroundColor = 0; /** - * Creates a mixin for managing system navigation bar. + * Creates a mixin for managing the system navigation bar. * * @param layout The layout this Mixin belongs to. - * @param window The window this activity of Mixin belongs to. - * @param attrs XML attributes given to the layout. - * @param defStyleAttr The default style attribute as given to the constructor of the layout. - * @param applyPartnerResources determine applies partner resources or not. + * @param window The window this activity of Mixin belongs to.* + * @param applyPartnerResources whether to apply partner resources or not. */ public SystemNavBarMixin( - @NonNull PartnerCustomizationLayout layout, - @NonNull Window window, - @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr, - boolean applyPartnerResources) { - partnerCustomizationLayout = layout; - windowOfActivity = window; - decorView = window.getDecorView(); + @NonNull TemplateLayout layout, @Nullable Window window, boolean applyPartnerResources) { + this.templateLayout = layout; + this.windowOfActivity = window; this.applyPartnerResources = applyPartnerResources; + } + /** + * Creates a mixin for managing the system navigation bar. + * + * @param attrs XML attributes given to the layout. + * @param defStyleAttr The default style attribute as given to the constructor of the layout. + */ + public void applyPartnerCustomizations(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { TypedArray a = - partnerCustomizationLayout + templateLayout .getContext() .obtainStyledAttributes(attrs, R.styleable.SucSystemNavBarMixin, defStyleAttr, 0); - int navigationBarBackground = + sucSystemNavBarBackgroundColor = a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarBackgroundColor, 0); - setSystemNavBarBackground(navigationBarBackground); + setSystemNavBarBackground(sucSystemNavBarBackgroundColor); setLightSystemNavBar( a.getBoolean(R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar())); a.recycle(); @@ -83,21 +89,20 @@ public class SystemNavBarMixin implements Mixin { * @param color The background color of navigation bar. */ public void setSystemNavBarBackground(int color) { - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) { if (applyPartnerResources) { - Context context = partnerCustomizationLayout.getContext(); + Context context = templateLayout.getContext(); color = PartnerConfigHelper.get(context) .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR); } - windowOfActivity.setNavigationBarColor(color); } } /** Returns the background color of navigation bar. */ public int getSystemNavBarBackground() { - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) { return windowOfActivity.getNavigationBarColor(); } return Color.BLACK; @@ -111,20 +116,25 @@ public class SystemNavBarMixin implements Mixin { * @param isLight true means compatible with light theme, otherwise compatible with dark theme */ public void setLightSystemNavBar(boolean isLight) { - if (Build.VERSION.SDK_INT >= VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= VERSION_CODES.O && windowOfActivity != null) { if (applyPartnerResources) { - Context context = partnerCustomizationLayout.getContext(); + Context context = templateLayout.getContext(); isLight = PartnerConfigHelper.get(context) .getBoolean(context, PartnerConfig.CONFIG_LIGHT_NAVIGATION_BAR, false); } - if (isLight) { - decorView.setSystemUiVisibility( - decorView.getSystemUiVisibility() | SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + windowOfActivity + .getDecorView() + .setSystemUiVisibility( + windowOfActivity.getDecorView().getSystemUiVisibility() + | SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); } else { - decorView.setSystemUiVisibility( - decorView.getSystemUiVisibility() & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + windowOfActivity + .getDecorView() + .setSystemUiVisibility( + windowOfActivity.getDecorView().getSystemUiVisibility() + & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); } } } @@ -134,10 +144,73 @@ public class SystemNavBarMixin implements Mixin { * should be drawn light-on-dark. */ public boolean isLightSystemNavBar() { - if (Build.VERSION.SDK_INT >= VERSION_CODES.O) { - return (decorView.getSystemUiVisibility() & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) + if (Build.VERSION.SDK_INT >= VERSION_CODES.O && windowOfActivity != null) { + return (windowOfActivity.getDecorView().getSystemUiVisibility() + & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) == SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; } return true; } + + /** + * Hides the navigation bar, make the color of the status and navigation bars transparent, and + * specify {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} flag so that the content is laid-out + * behind the transparent status bar. This is commonly used with {@link + * android.app.Activity#getWindow()} to make the navigation and status bars follow the Setup + * Wizard style. + * + * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + */ + public void hideSystemBars(final Window window) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + SystemBarBaseHelper.addVisibilityFlag(window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarBaseHelper.addImmersiveFlagsToDecorView( + window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + + // Also set the navigation bar and status bar to transparent color. Note that this + // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. + window.setNavigationBarColor(Color.TRANSPARENT); + window.setStatusBarColor(Color.TRANSPARENT); + } + } + + /** + * Reverts the actions of hideSystemBars. Note that this will remove the system UI visibility + * flags regardless of whether it is originally present. The status bar color is reset to + * transparent, thus it will show the status bar color set by StatusBarMixin. + * + * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + */ + public void showSystemBars(final Window window, final Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + SystemBarBaseHelper.removeVisibilityFlag(window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarBaseHelper.removeImmersiveFlagsFromDecorView( + window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + + if (context != null) { + if (applyPartnerResources) { + int partnerNavigationBarColor = + PartnerConfigHelper.get(context) + .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR); + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(partnerNavigationBarColor); + } else { + if (templateLayout instanceof PartnerCustomizationLayout) { + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(sucSystemNavBarBackgroundColor); + } else { + // noinspection AndroidLintInlinedApi + final TypedArray typedArray = + context.obtainStyledAttributes( + new int[] {android.R.attr.statusBarColor, android.R.attr.navigationBarColor}); + final int statusBarColor = typedArray.getColor(0, 0); + final int navigationBarColor = typedArray.getColor(1, 0); + window.setStatusBarColor(statusBarColor); + window.setNavigationBarColor(navigationBarColor); + typedArray.recycle(); + } + } + } + } + } } diff --git a/main/java/com/google/android/setupcompat/util/ResourceEntry.java b/main/java/com/google/android/setupcompat/util/ResourceEntry.java deleted file mode 100644 index c1dee67..0000000 --- a/main/java/com/google/android/setupcompat/util/ResourceEntry.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 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.util; - -import android.os.Bundle; -import androidx.annotation.VisibleForTesting; - -/** - * 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 - * on IPC via the Bundle representation. - */ -public final class ResourceEntry { - @VisibleForTesting static final String KEY_PACKAGE_NAME = "packageName"; - @VisibleForTesting static final String KEY_RESOURCE_NAME = "resourceName"; - @VisibleForTesting static final String KEY_RESOURCE_ID = "resourceId"; - - private final String packageName; - private final String resourceName; - private final int resourceId; - - /** - * Creates a {@code ResourceEntry} object from a provided bundle. - * - * @param bundle the source bundle needs to have all the information for a {@code ResourceEntry} - */ - public static ResourceEntry fromBundle(Bundle bundle) { - String packageName; - String resourceName; - int resourceId; - if (!bundle.containsKey(KEY_PACKAGE_NAME) - || !bundle.containsKey(KEY_RESOURCE_NAME) - || !bundle.containsKey(KEY_RESOURCE_ID)) { - return null; - } - packageName = bundle.getString(KEY_PACKAGE_NAME); - resourceName = bundle.getString(KEY_RESOURCE_NAME); - resourceId = bundle.getInt(KEY_RESOURCE_ID); - return new ResourceEntry(packageName, resourceName, resourceId); - } - - public ResourceEntry(String packageName, String resourceName, int resourceId) { - this.packageName = packageName; - this.resourceName = resourceName; - this.resourceId = resourceId; - } - - public String getPackageName() { - return this.packageName; - } - - public String getResourceName() { - return this.resourceName; - } - - public int getResourceId() { - return this.resourceId; - } - - /** - * Returns a bundle representation of this resource entry, which can then be sent over IPC. - * - * @see #fromBundle(Bundle) - */ - public Bundle toBundle() { - Bundle result = new Bundle(); - result.putString(KEY_PACKAGE_NAME, packageName); - result.putString(KEY_RESOURCE_NAME, resourceName); - result.putInt(KEY_RESOURCE_ID, resourceId); - return result; - } -} diff --git a/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java new file mode 100644 index 0000000..a1c5f65 --- /dev/null +++ b/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2019 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.util; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +/** + * A helper class to manage the system navigation bar and status bar. This will add various + * systemUiVisibility flags to the given Window or View to make them follow the Setup Wizard style. + * + * <p>When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the + * system bars using methods from this class. For Lollipop, {@link + * #hideSystemBars(android.view.Window)} will completely hide the system navigation bar and change + * the status bar to transparent, and layout the screen contents (usually the illustration) behind + * it. + */ +public class SystemBarBaseHelper { + + private static final String TAG = "SystemBarBaseHelper"; + + @SuppressLint("InlinedApi") + public static final int DEFAULT_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + @SuppressLint("InlinedApi") + public static final int DIALOG_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + /** + * The maximum number of retries when peeking the decor view. When polling for the decor view, + * waiting it to be installed, set a maximum number of retries. + */ + private static final int PEEK_DECOR_VIEW_RETRIES = 3; + + /** Convenience method to add a visibility flag in addition to the existing ones. */ + public static void addVisibilityFlag(final View view, final int flag) { + final int vis = view.getSystemUiVisibility(); + view.setSystemUiVisibility(vis | flag); + } + + /** Convenience method to add a visibility flag in addition to the existing ones. */ + public static void addVisibilityFlag(final Window window, final int flag) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility |= flag; + window.setAttributes(attrs); + } + + /** + * Convenience method to remove a visibility flag from the view, leaving other flags that are not + * specified intact. + */ + public static void removeVisibilityFlag(final View view, final int flag) { + final int vis = view.getSystemUiVisibility(); + view.setSystemUiVisibility(vis & ~flag); + } + + /** + * Convenience method to remove a visibility flag from the window, leaving other flags that are + * not specified intact. + */ + public static void removeVisibilityFlag(final Window window, final int flag) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility &= ~flag; + window.setAttributes(attrs); + } + + /** + * Add the specified immersive flags to the decor view of the window, because {@link + * View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view instead of + * the window. + */ + public static void addImmersiveFlagsToDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + addVisibilityFlag(decorView, vis); + } + }); + } + + public static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + removeVisibilityFlag(decorView, vis); + } + }); + } + + private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { + new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); + } + + private static class DecorViewFinder { + + private final Handler handler = new Handler(); + private Window window; + private int retries; + private OnDecorViewInstalledListener callback; + + private final Runnable checkDecorViewRunnable = + new Runnable() { + @Override + public void run() { + // Use peekDecorView instead of getDecorView so that clients can still set window + // features after calling this method. + final View decorView = window.peekDecorView(); + if (decorView != null) { + callback.onDecorViewInstalled(decorView); + } else { + retries--; + if (retries >= 0) { + // If the decor view is not installed yet, try again in the next loop. + handler.post(checkDecorViewRunnable); + } else { + Log.w(TAG, "Cannot get decor view of window: " + window); + } + } + } + }; + + public void getDecorView(Window window, OnDecorViewInstalledListener callback, int retries) { + this.window = window; + this.retries = retries; + this.callback = callback; + checkDecorViewRunnable.run(); + } + } + + private interface OnDecorViewInstalledListener { + + void onDecorViewInstalled(View decorView); + } +} |