summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:35:10 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:35:10 +0000
commit8e371ee28266893dc042a67787b691d43c64d211 (patch)
tree096454abf50a73063ca997f864b3d5f3255a5899
parentd497370655ea8c6f15169af88cd9c75406e51395 (diff)
parent33cf4c96e8c4986edf036ec2c51228d5b9905bd2 (diff)
downloadsetupcompat-android12-mainline-statsd-release.tar.gz
Snap for 7550844 from 33cf4c96e8c4986edf036ec2c51228d5b9905bd2 to mainline-os-statsd-releaseandroid-mainline-12.0.0_r84android-mainline-12.0.0_r58android12-mainline-statsd-release
Change-Id: Ibe72c35838c910061205546f4b5b000735d33e3d
-rw-r--r--Android.bp71
-rw-r--r--METADATA3
-rw-r--r--NOTICE202
-rw-r--r--exempting_lint_checks.txt7
-rw-r--r--grandfathered_lint_checks.txt0
-rw-r--r--lint-baseline.xml59
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl72
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl55
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl22
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl40
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl19
-rw-r--r--main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl19
-rw-r--r--main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java85
-rw-r--r--main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java2
-rw-r--r--main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java34
-rw-r--r--main/java/com/google/android/setupcompat/internal/PersistableBundles.java10
-rw-r--r--main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java21
-rw-r--r--main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java40
-rw-r--r--main/java/com/google/android/setupcompat/internal/TemplateLayout.java6
-rw-r--r--main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java12
-rw-r--r--main/java/com/google/android/setupcompat/portal/NotificationComponent.java114
-rw-r--r--main/java/com/google/android/setupcompat/portal/PortalConstants.java72
-rw-r--r--main/java/com/google/android/setupcompat/portal/PortalHelper.java301
-rw-r--r--main/java/com/google/android/setupcompat/portal/PortalResultHelper.java49
-rw-r--r--main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java250
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterActionButton.java2
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterBarMixin.java360
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterButton.java63
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterButtonInflater.java2
-rw-r--r--main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java412
-rw-r--r--main/java/com/google/android/setupcompat/template/StatusBarMixin.java10
-rw-r--r--main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java59
-rw-r--r--main/java/com/google/android/setupcompat/util/BuildCompatUtils.java66
-rw-r--r--main/java/com/google/android/setupcompat/util/Logger.java83
-rw-r--r--main/java/com/google/android/setupcompat/util/SystemBarHelper.java7
-rw-r--r--main/java/com/google/android/setupcompat/util/WizardManagerHelper.java28
-rw-r--r--main/res/values/attrs.xml14
-rw-r--r--main/res/values/dimens.xml29
-rw-r--r--main/res/values/styles.xml22
-rw-r--r--partnerconfig/AndroidManifest.xml13
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java237
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java318
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java264
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java2
-rw-r--r--partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java30
45 files changed, 2974 insertions, 612 deletions
diff --git a/Android.bp b/Android.bp
index 783b394..01e654d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2,6 +2,69 @@
// Build the setup compat library.
//
+package {
+ default_applicable_licenses: ["external_setupcompat_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "external_setupcompat_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
+
+filegroup {
+ name: "Aidls",
+ srcs: [
+ "main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl",
+ ],
+ path: "main/aidl",
+}
+
+filegroup {
+ name: "AidlsPortal",
+ srcs: [
+ "main/aidl/com/google/android/setupcompat/portal/*.aidl",
+ ],
+ path: "main/aidl",
+}
+
+filegroup {
+ name: "Srcs",
+ srcs: [
+ "main/java/com/google/android/setupcompat/*.java",
+ "main/java/com/google/android/setupcompat/internal/*.java",
+ "main/java/com/google/android/setupcompat/logging/*.java",
+ "main/java/com/google/android/setupcompat/logging/internal/*.java",
+ "main/java/com/google/android/setupcompat/template/*.java",
+ "main/java/com/google/android/setupcompat/util/*.java",
+ "main/java/com/google/android/setupcompat/view/*.java",
+ ],
+ path: "main/java",
+}
+
+filegroup {
+ name: "SrcsPartnerConfig",
+ srcs: [
+ "partnerconfig/java/**/*.java",
+ ],
+ path: "partnerconfig/java",
+}
+
+filegroup {
+ name: "SrcsPortal",
+ srcs: [
+ "main/java/com/google/android/setupcompat/portal/*.java",
+ ],
+ path: "main/java",
+}
+
android_library {
name: "setupcompat",
manifest: "AndroidManifest.xml",
@@ -9,9 +72,11 @@ android_library {
"main/res",
],
srcs: [
- "main/java/**/*.java",
- "partnerconfig/java/**/*.java",
- "main/aidl/**/*.aidl",
+ ":Aidls",
+ ":AidlsPortal",
+ ":Srcs",
+ ":SrcsPartnerConfig",
+ ":SrcsPortal",
],
static_libs: [
"androidx.annotation_annotation",
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 7a4a3ea..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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. \ No newline at end of file
diff --git a/exempting_lint_checks.txt b/exempting_lint_checks.txt
new file mode 100644
index 0000000..90b8caf
--- /dev/null
+++ b/exempting_lint_checks.txt
@@ -0,0 +1,7 @@
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/util/SystemBarHelper.java: AnnotateVersionCheck: public static void setImeInsetView(final View view) {
diff --git a/grandfathered_lint_checks.txt b/grandfathered_lint_checks.txt
deleted file mode 100644
index e69de29..0000000
--- a/grandfathered_lint_checks.txt
+++ /dev/null
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..43e81f8
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+ <issue
+ id="NewApi"
+ message="`android:Widget.Material.Button.Colored` requires API level 23 (current min is 14)"
+ errorLine1=" &lt;style name=&quot;SucPartnerCustomizationButton.Primary&quot; parent=&quot;android:Widget.Material.Button.Colored&quot;>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/setupcompat/main/res/values/styles.xml"
+ line="40"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="`android:fontFamily` requires API level 16 (current min is 14)"
+ errorLine1=" &lt;item name=&quot;android:fontFamily&quot;>?attr/sucFooterBarButtonFontFamily&lt;/item>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/setupcompat/main/res/values/styles.xml"
+ line="48"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="`android:stateListAnimator` requires API level 21 (current min is 14)"
+ errorLine1=" &lt;item name=&quot;android:stateListAnimator&quot;>@null&lt;/item>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/setupcompat/main/res/values/styles.xml"
+ line="54"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="`android:Widget.Material.Button.Borderless.Colored` requires API level 21 (current min is 14)"
+ errorLine1=" &lt;style name=&quot;SucPartnerCustomizationButton.Secondary&quot; parent=&quot;android:Widget.Material.Button.Borderless.Colored&quot;>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/setupcompat/main/res/values/styles.xml"
+ line="60"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="`android:fontFamily` requires API level 16 (current min is 14)"
+ errorLine1=" &lt;item name=&quot;android:fontFamily&quot;>?attr/sucFooterBarButtonFontFamily&lt;/item>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/setupcompat/main/res/values/styles.xml"
+ line="68"
+ column="15"/>
+ </issue>
+
+</issues>
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl
new file mode 100644
index 0000000..13303d8
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.os.Bundle;
+
+/**
+ * Interface for progress service to update progress to SUW. Clients should
+ * update progress at least once a minute, or set a pending reason to stop
+ * update progress. Without progress update and pending reason. We considering
+ * the progress service is no response will suspend it and unbinde service.
+ */
+interface IPortalProgressCallback {
+ /**
+ * Sets completed amount.
+ */
+ Bundle setProgressCount(int completed, int failed, int total) = 0;
+
+ /**
+ * Sets completed percentage.
+ */
+ Bundle setProgressPercentage(int percentage) = 1;
+
+ /**
+ * Sets the summary shows on portal activity.
+ */
+ Bundle setSummary(String summary) = 2;
+
+ /**
+ * Let SUW knows the progress is pending now, and stop update progress.
+ * @param reasonResId The resource identifier.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules
+ * @param formatArgs The format arguments that will be used for substitution.
+ */
+ Bundle setPendingReason(int reasonResId, int quantity, in int[] formatArgs, int reason) = 3;
+
+ /**
+ * Once the progress completed, and service can be destroy. Call this function.
+ * SUW will unbind the service.
+ * @param resId The resource identifier.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules
+ * @param formatArgs The format arguments that will be used for substitution.
+ */
+ Bundle setComplete(int resId, int quantity, in int[] formatArgs) = 4;
+
+ /**
+ * Once the progress failed, and not able to finish the progress. Should call
+ * this function. SUW will unbind service after receive setFailure. Client can
+ * registerProgressService again once the service is ready to execute.
+ * @param resId The resource identifier.
+ * @param quantity The number used to get the correct string for the current language's
+ * plural rules
+ * @param formatArgs The format arguments that will be used for substitution.
+ */
+ Bundle setFailure(int resId, int quantity, in int[] formatArgs) = 5;
+} \ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl
new file mode 100644
index 0000000..d741125
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.os.Bundle;
+import com.google.android.setupcompat.portal.IPortalProgressCallback;
+
+/**
+ * Interface of progress service, all servics needs to run during onboarding, and would like
+ * to consolidate notifications with SetupNotificationService, should implement this interface.
+ * So that SetupNotificationService can bind the progress service and run below
+ * SetupNotificationService.
+ */
+interface IPortalProgressService {
+ /**
+ * Called when the Portal notification is started.
+ */
+ oneway void onPortalSessionStart() = 0;
+
+ /**
+ * Provides a non-null callback after service connected.
+ */
+ oneway void onSetCallback(IPortalProgressCallback callback) = 1;
+
+ /**
+ * Called when progress timed out.
+ */
+ oneway void onSuspend() = 2;
+
+ /**
+ * User clicks "User mobile data" on portal activity.
+ * All running progress should agree to use mobile data.
+ */
+ oneway void onAllowMobileData(boolean allowed) = 3;
+
+ /**
+ * Portal service calls to get remaining downloading size(MB) from progress service.
+ */
+ @nullable
+ Bundle onGetRemainingValues() = 4;
+} \ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl
new file mode 100644
index 0000000..56d39e5
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+/** Listener to listen the result of registerProgressService */
+interface IPortalRegisterResultListener {
+ void onResult(in Bundle result) = 0;
+} \ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl b/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl
new file mode 100644
index 0000000..9f9b1d8
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.os.UserHandle;
+import com.google.android.setupcompat.portal.IPortalRegisterResultListener;
+import com.google.android.setupcompat.portal.NotificationComponent;
+import com.google.android.setupcompat.portal.ProgressServiceComponent;
+
+/**
+ * Declares the interface for notification related service methods.
+ */
+interface ISetupNotificationService {
+ /** Register a notification without progress service */
+ boolean registerNotification(in NotificationComponent component) = 0;
+ void unregisterNotification(in NotificationComponent component) = 1;
+
+ /** Register a progress service */
+ void registerProgressService(in ProgressServiceComponent component, in UserHandle userHandle, IPortalRegisterResultListener listener) = 2;
+
+ /** Checks the progress connection still alive or not. */
+ boolean isProgressServiceAlive(in ProgressServiceComponent component, in UserHandle userHandle) = 3;
+
+ /** Checks portal avaailable or not. */
+ boolean isPortalAvailable() = 4;
+} \ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl b/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl
new file mode 100644
index 0000000..5de3f76
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+parcelable NotificationComponent; \ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl b/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl
new file mode 100644
index 0000000..6a6e120
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.google.android.setupcompat.portal;
+
+ parcelable ProgressServiceComponent; \ No newline at end of file
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index fac4b39..e5ba0c5 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -26,7 +26,6 @@ import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.PersistableBundle;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -42,12 +41,14 @@ import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.template.StatusBarMixin;
import com.google.android.setupcompat.template.SystemNavBarMixin;
+import com.google.android.setupcompat.util.BuildCompatUtils;
+import com.google.android.setupcompat.util.Logger;
import com.google.android.setupcompat.util.WizardManagerHelper;
/** A templatization layout with consistent style used in Setup Wizard or app itself. */
public class PartnerCustomizationLayout extends TemplateLayout {
- // Log tags can have at most 23 characters on N or before.
- private static final String TAG = "PartnerCustomizedLayout";
+
+ private static final Logger LOG = new Logger("PartnerCustomizationLayout");
/**
* Attribute indicating whether usage of partner theme resources is allowed. This corresponds to
@@ -56,6 +57,18 @@ public class PartnerCustomizationLayout extends TemplateLayout {
*/
private boolean usePartnerResourceAttr;
+ /**
+ * Attribute indicating whether using full dynamic colors or not. This corresponds to the {@code
+ * app:sucFullDynamicColor} XML attribute.
+ */
+ private boolean useFullDynamicColorAttr;
+
+ /**
+ * Attribute indicating whether usage of dynamic is allowed. This corresponds to the existence of
+ * {@code app:sucFullDynamicColor} XML attribute.
+ */
+ private boolean useDynamicColor;
+
private Activity activity;
public PartnerCustomizationLayout(Context context) {
@@ -83,6 +96,9 @@ public class PartnerCustomizationLayout extends TemplateLayout {
}
private void init(AttributeSet attrs, int defStyleAttr) {
+ if (isInEditMode()) {
+ return;
+ }
TypedArray a =
getContext()
@@ -132,15 +148,13 @@ public class PartnerCustomizationLayout extends TemplateLayout {
@Override
protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {
- boolean isSetupFlow;
-
// Sets default value to true since this timing
// before PartnerCustomization members initialization
usePartnerResourceAttr = true;
activity = lookupActivityFromContext(getContext());
- isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
+ boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
TypedArray a =
getContext()
@@ -149,27 +163,32 @@ public class PartnerCustomizationLayout extends TemplateLayout {
if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) {
// TODO: Enable Log.WTF after other client already set sucUsePartnerResource.
- Log.e(TAG, "Attribute sucUsePartnerResource not found in " + activity.getComponentName());
+ LOG.e("Attribute sucUsePartnerResource not found in " + activity.getComponentName());
}
usePartnerResourceAttr =
isSetupFlow
|| a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true);
+ useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor);
+ useFullDynamicColorAttr =
+ a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false);
+
a.recycle();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(
- TAG,
- "activity="
- + activity.getClass().getSimpleName()
- + " isSetupFlow="
- + isSetupFlow
- + " enablePartnerResourceLoading="
- + enablePartnerResourceLoading()
- + " usePartnerResourceAttr="
- + usePartnerResourceAttr);
- }
+ LOG.atDebug(
+ "activity="
+ + activity.getClass().getSimpleName()
+ + " isSetupFlow="
+ + isSetupFlow
+ + " enablePartnerResourceLoading="
+ + enablePartnerResourceLoading()
+ + " usePartnerResourceAttr="
+ + usePartnerResourceAttr
+ + " useDynamicColor="
+ + useDynamicColor
+ + " useFullDynamicColorAttr="
+ + useFullDynamicColorAttr);
}
@Override
@@ -216,7 +235,7 @@ public class PartnerCustomizationLayout extends TemplateLayout {
}
}
- private static Activity lookupActivityFromContext(Context context) {
+ public static Activity lookupActivityFromContext(Context context) {
if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextWrapper) {
@@ -252,4 +271,30 @@ public class PartnerCustomizationLayout extends TemplateLayout {
}
return true;
}
+
+ /**
+ * Returns {@code true} if the current layout/activity applies dynamic color. Otherwise, returns
+ * {@code false}.
+ */
+ public boolean shouldApplyDynamicColor() {
+ if (!useDynamicColor) {
+ return false;
+ }
+ if (!BuildCompatUtils.isAtLeastS()) {
+ return false;
+ }
+ if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if the current layout/activity applies full dynamic color. Otherwise,
+ * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()}
+ * and the value of the {@code app:sucFullDynamicColor}.
+ */
+ public boolean useFullDynamicColor() {
+ return shouldApplyDynamicColor() && useFullDynamicColorAttr;
+ }
}
diff --git a/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
index af17a62..574f614 100644
--- a/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
+++ b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
@@ -18,8 +18,8 @@ package com.google.android.setupcompat.internal;
import android.content.Context;
import android.content.res.Resources.Theme;
-import androidx.annotation.StyleRes;
import android.view.ContextThemeWrapper;
+import androidx.annotation.StyleRes;
/**
* Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence over
diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
index 39b50cf..5f8bf67 100644
--- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
+++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
@@ -27,7 +27,9 @@ public class FooterButtonPartnerConfig {
private final PartnerConfig buttonIconConfig;
private final PartnerConfig buttonTextColorConfig;
private final PartnerConfig buttonTextSizeConfig;
+ private final PartnerConfig buttonMinHeightConfig;
private final PartnerConfig buttonTextTypeFaceConfig;
+ private final PartnerConfig buttonTextStyleConfig;
private final PartnerConfig buttonRadiusConfig;
private final PartnerConfig buttonRippleColorAlphaConfig;
private final int partnerTheme;
@@ -40,14 +42,18 @@ public class FooterButtonPartnerConfig {
PartnerConfig buttonIconConfig,
PartnerConfig buttonTextColorConfig,
PartnerConfig buttonTextSizeConfig,
+ PartnerConfig buttonMinHeightConfig,
PartnerConfig buttonTextTypeFaceConfig,
+ PartnerConfig buttonTextStyleConfig,
PartnerConfig buttonRadiusConfig,
PartnerConfig buttonRippleColorAlphaConfig) {
this.partnerTheme = partnerTheme;
this.buttonTextColorConfig = buttonTextColorConfig;
this.buttonTextSizeConfig = buttonTextSizeConfig;
+ this.buttonMinHeightConfig = buttonMinHeightConfig;
this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig;
+ this.buttonTextStyleConfig = buttonTextStyleConfig;
this.buttonBackgroundConfig = buttonBackgroundConfig;
this.buttonDisableAlphaConfig = buttonDisableAlphaConfig;
this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig;
@@ -80,6 +86,10 @@ public class FooterButtonPartnerConfig {
return buttonTextColorConfig;
}
+ public PartnerConfig getButtonMinHeightConfig() {
+ return buttonMinHeightConfig;
+ }
+
public PartnerConfig getButtonTextSizeConfig() {
return buttonTextSizeConfig;
}
@@ -88,6 +98,10 @@ public class FooterButtonPartnerConfig {
return buttonTextTypeFaceConfig;
}
+ public PartnerConfig getButtonTextStyleConfig() {
+ return buttonTextStyleConfig;
+ }
+
public PartnerConfig getButtonRadiusConfig() {
return buttonRadiusConfig;
}
@@ -105,15 +119,19 @@ public class FooterButtonPartnerConfig {
private PartnerConfig buttonIconConfig = null;
private PartnerConfig buttonTextColorConfig = null;
private PartnerConfig buttonTextSizeConfig = null;
+ private PartnerConfig buttonMinHeight = null;
private PartnerConfig buttonTextTypeFaceConfig = null;
+ private PartnerConfig buttonTextStyleConfig = null;
private PartnerConfig buttonRadiusConfig = null;
private PartnerConfig buttonRippleColorAlphaConfig = null;
private int partnerTheme;
public Builder(FooterButton footerButton) {
this.footerButton = footerButton;
- // default partnerTheme should be the same as footerButton.getTheme();
- this.partnerTheme = this.footerButton.getTheme();
+ if (this.footerButton != null) {
+ // default partnerTheme should be the same as footerButton.getTheme();
+ this.partnerTheme = this.footerButton.getTheme();
+ }
}
public Builder setButtonBackgroundConfig(PartnerConfig buttonBackgroundConfig) {
@@ -146,11 +164,21 @@ public class FooterButtonPartnerConfig {
return this;
}
+ public Builder setButtonMinHeight(PartnerConfig buttonMinHeightConfig) {
+ this.buttonMinHeight = buttonMinHeightConfig;
+ return this;
+ }
+
public Builder setTextTypeFaceConfig(PartnerConfig buttonTextTypeFaceConfig) {
this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig;
return this;
}
+ public Builder setTextStyleConfig(PartnerConfig buttonTextStyleConfig) {
+ this.buttonTextStyleConfig = buttonTextStyleConfig;
+ return this;
+ }
+
public Builder setButtonRadiusConfig(PartnerConfig buttonRadiusConfig) {
this.buttonRadiusConfig = buttonRadiusConfig;
return this;
@@ -175,7 +203,9 @@ public class FooterButtonPartnerConfig {
buttonIconConfig,
buttonTextColorConfig,
buttonTextSizeConfig,
+ buttonMinHeight,
buttonTextTypeFaceConfig,
+ buttonTextStyleConfig,
buttonRadiusConfig,
buttonRippleColorAlphaConfig);
}
diff --git a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
index 1197645..3b7d5a5 100644
--- a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
+++ b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
@@ -22,16 +22,18 @@ import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.ArrayMap;
-import android.util.Log;
+import com.google.android.setupcompat.util.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Contains utility methods related to {@link PersistableBundle}. */
-@TargetApi(VERSION_CODES.Q)
+@TargetApi(VERSION_CODES.LOLLIPOP_MR1)
public final class PersistableBundles {
+ private static final Logger LOG = new Logger("PersistableBundles");
+
/**
* Merges two or more {@link PersistableBundle}. Ensures no conflict of keys occurred during
* merge.
@@ -121,7 +123,7 @@ public final class PersistableBundles {
for (String key : baseBundle.keySet()) {
Object value = baseBundle.get(key);
if (!isSupportedDataType(value)) {
- Log.w(TAG, String.format("Unknown/unsupported data type [%s] for key %s", value, key));
+ LOG.w(String.format("Unknown/unsupported data type [%s] for key %s", value, key));
continue;
}
map.put(key, baseBundle.get(key));
@@ -141,6 +143,4 @@ public final class PersistableBundles {
private PersistableBundles() {
throw new AssertionError("Should not be instantiated");
}
-
- private static final String TAG = "SetupCompat.PersistBls";
}
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index a1ca156..149da54 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -21,9 +21,9 @@ import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.VisibleForTesting;
-import android.util.Log;
import com.google.android.setupcompat.ISetupCompatService;
import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType;
+import com.google.android.setupcompat.util.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
@@ -41,11 +41,14 @@ import java.util.concurrent.TimeoutException;
*/
public class SetupCompatServiceInvoker {
+ private static final Logger LOG = new Logger("SetupCompatServiceInvoker");
+
+ @SuppressLint("DefaultLocale")
public void logMetricEvent(@MetricType int metricType, Bundle args) {
try {
loggingExecutor.execute(() -> invokeLogMetric(metricType, args));
} catch (RejectedExecutionException e) {
- Log.e(TAG, String.format("Metric of type %d dropped since queue is full.", metricType), e);
+ LOG.e(String.format("Metric of type %d dropped since queue is full.", metricType), e);
}
}
@@ -53,7 +56,7 @@ public class SetupCompatServiceInvoker {
try {
setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
} catch (RejectedExecutionException e) {
- Log.e(TAG, String.format("Screen %s bind back fail.", screenName), e);
+ LOG.e(String.format("Screen %s bind back fail.", screenName), e);
}
}
@@ -66,10 +69,10 @@ public class SetupCompatServiceInvoker {
if (setupCompatService != null) {
setupCompatService.logMetric(metricType, args, Bundle.EMPTY);
} else {
- Log.w(TAG, "logMetric failed since service reference is null. Are the permissions valid?");
+ LOG.w("logMetric failed since service reference is null. Are the permissions valid?");
}
- } catch (InterruptedException | TimeoutException | RemoteException e) {
- Log.e(TAG, String.format("Exception occurred while trying to log metric = [%s]", args), e);
+ } catch (InterruptedException | TimeoutException | RemoteException | IllegalStateException e) {
+ LOG.e(String.format("Exception occurred while trying to log metric = [%s]", args), e);
}
}
@@ -81,11 +84,10 @@ public class SetupCompatServiceInvoker {
if (setupCompatService != null) {
setupCompatService.validateActivity(screenName, bundle);
} else {
- Log.w(TAG, "BindBack failed since service reference is null. Are the permissions valid?");
+ LOG.w("BindBack failed since service reference is null. Are the permissions valid?");
}
} catch (InterruptedException | TimeoutException | RemoteException e) {
- Log.e(
- TAG,
+ LOG.e(
String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
e);
}
@@ -125,5 +127,4 @@ public class SetupCompatServiceInvoker {
private static SetupCompatServiceInvoker instance;
private static final long MAX_WAIT_TIME_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(10);
- private static final String TAG = "SucServiceInvoker";
}
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
index 2043a81..e75d991 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
@@ -26,8 +26,8 @@ import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import android.util.Log;
import com.google.android.setupcompat.ISetupCompatService;
+import com.google.android.setupcompat.util.Logger;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -40,6 +40,8 @@ import java.util.function.UnaryOperator;
*/
public class SetupCompatServiceProvider {
+ private static final Logger LOG = new Logger("SetupCompatServiceProvider");
+
/**
* Returns an instance of {@link ISetupCompatService} if one already exists. If not, attempts to
* rebind if the current state allows such an operation and waits until {@code waitTime} for
@@ -94,7 +96,7 @@ public class SetupCompatServiceProvider {
}
CountDownLatch connectedStateLatch = getConnectedCondition();
- Log.i(TAG, "Waiting for service to get connected");
+ LOG.atInfo("Waiting for service to get connected");
boolean stateChanged = connectedStateLatch.await(timeout, timeUnit);
if (!stateChanged) {
// Even though documentation states that disconnected service should connect again,
@@ -104,13 +106,10 @@ public class SetupCompatServiceProvider {
String.format("Failed to acquire connection after [%s %s]", timeout, timeUnit));
}
currentServiceState = getCurrentServiceState();
- if (Log.isLoggable(TAG, Log.INFO)) {
- Log.i(
- TAG,
- String.format(
- "Finished waiting for service to get connected. Current state = %s",
- currentServiceState.state));
- }
+ LOG.atInfo(
+ String.format(
+ "Finished waiting for service to get connected. Current state = %s",
+ currentServiceState.state));
return currentServiceState.compatService;
}
@@ -126,11 +125,11 @@ public class SetupCompatServiceProvider {
private synchronized void requestServiceBind() {
ServiceContext currentServiceState = getCurrentServiceState();
if (currentServiceState.state == State.CONNECTED) {
- Log.i(TAG, "Refusing to rebind since current state is already connected");
+ LOG.atInfo("Refusing to rebind since current state is already connected");
return;
}
if (currentServiceState.state != State.NOT_STARTED) {
- Log.i(TAG, "Unbinding existing service connection.");
+ LOG.atInfo("Unbinding existing service connection.");
context.unbindService(serviceConnection);
}
@@ -139,7 +138,7 @@ public class SetupCompatServiceProvider {
bindAllowed =
context.bindService(COMPAT_SERVICE_INTENT, serviceConnection, Context.BIND_AUTO_CREATE);
} catch (SecurityException e) {
- Log.e(TAG, "Unable to bind to compat service", e);
+ LOG.e("Unable to bind to compat service. " + e);
bindAllowed = false;
}
@@ -149,12 +148,12 @@ public class SetupCompatServiceProvider {
// in the normal world
if (getCurrentState() != State.CONNECTED) {
swapServiceContextAndNotify(new ServiceContext(State.BINDING));
- Log.i(TAG, "Context#bindService went through, now waiting for service connection");
+ LOG.atInfo("Context#bindService went through, now waiting for service connection");
}
} else {
// SetupWizard is not installed/calling app does not have permissions to bind.
swapServiceContextAndNotify(new ServiceContext(State.BIND_FAILED));
- Log.e(TAG, "Context#bindService did not succeed.");
+ LOG.e("Context#bindService did not succeed.");
}
}
@@ -174,12 +173,9 @@ public class SetupCompatServiceProvider {
}
private void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
- if (Log.isLoggable(TAG, Log.INFO)) {
- Log.i(
- TAG,
- String.format(
- "State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
- }
+ LOG.atInfo(
+ String.format("State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
+
serviceContext = latestServiceContext;
CountDownLatch countDownLatch = getAndClearConnectedCondition();
if (countDownLatch != null) {
@@ -221,7 +217,7 @@ public class SetupCompatServiceProvider {
State state = State.CONNECTED;
if (binder == null) {
state = State.DISCONNECTED;
- Log.w(TAG, "Binder is null when onServiceConnected was called!");
+ LOG.w("Binder is null when onServiceConnected was called!");
}
swapServiceContextAndNotify(
new ServiceContext(state, ISetupCompatService.Stub.asInterface(binder)));
@@ -336,6 +332,4 @@ public class SetupCompatServiceProvider {
// lint error.
@SuppressLint("StaticFieldLeak")
private static volatile SetupCompatServiceProvider instance;
-
- private static final String TAG = "SucServiceProvider";
}
diff --git a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
index 34179d6..25a3c5b 100644
--- a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
+++ b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
@@ -20,15 +20,15 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.Keep;
-import androidx.annotation.LayoutRes;
-import androidx.annotation.StyleRes;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
+import androidx.annotation.Keep;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StyleRes;
import com.google.android.setupcompat.R;
import com.google.android.setupcompat.template.Mixin;
import java.util.HashMap;
diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
index 7d0b731..2aa1240 100644
--- a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
+++ b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
@@ -30,24 +30,28 @@ import com.google.android.setupcompat.logging.SetupMetricsLogger;
@TargetApi(VERSION_CODES.Q)
public class PartnerCustomizedResourceListMetric {
- public static void logMetrics(Context context, String screenName, Bundle bundle) {
+ public static void logMetrics(Context context, String deviceDisplayName, Bundle bundle) {
PersistableBundle logBundle =
- buildLogBundleFromResourceConfigBundle(context.getPackageName(), bundle);
+ buildLogBundleFromResourceConfigBundle(context.getPackageName(), deviceDisplayName, bundle);
if (!logBundle.isEmpty()) {
SetupMetricsLogger.logCustomEvent(
context,
- CustomEvent.create(MetricKey.get("PartnerCustomizationResource", screenName), logBundle));
+ CustomEvent.create(
+ MetricKey.get("PartnerCustomizationResource", "NoScreenName"), logBundle));
}
}
@VisibleForTesting
public static PersistableBundle buildLogBundleFromResourceConfigBundle(
- String defaultPackageName, Bundle resourceConfigBundle) {
+ String defaultPackageName, String deviceDisplayName, Bundle resourceConfigBundle) {
PersistableBundle persistableBundle = new PersistableBundle();
+ persistableBundle.putString("deviceDisplayName", deviceDisplayName);
for (String key : resourceConfigBundle.keySet()) {
Bundle resourceExtra = resourceConfigBundle.getBundle(key);
if (!resourceExtra.getString("packageName", defaultPackageName).equals(defaultPackageName)) {
persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), true);
+ } else {
+ persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), false);
}
}
diff --git a/main/java/com/google/android/setupcompat/portal/NotificationComponent.java b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java
new file mode 100644
index 0000000..a90963b
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class that represents how a persistent notification is to be presented to the user using the
+ * {@link com.google.android.setupcompat.portal.ISetupNotificationService}.
+ */
+public class NotificationComponent implements Parcelable {
+
+ @NotificationType private final int notificationType;
+ private Bundle extraBundle = new Bundle();
+
+ private NotificationComponent(@NotificationType int notificationType) {
+ this.notificationType = notificationType;
+ }
+
+ protected NotificationComponent(Parcel in) {
+ this(in.readInt());
+ extraBundle = in.readBundle(Bundle.class.getClassLoader());
+ }
+
+ public int getIntExtra(String key, int defValue) {
+ return extraBundle.getInt(key, defValue);
+ }
+
+ @NotificationType
+ public int getNotificationType() {
+ return notificationType;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(notificationType);
+ dest.writeBundle(extraBundle);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<NotificationComponent> CREATOR =
+ new Creator<NotificationComponent>() {
+ @Override
+ public NotificationComponent createFromParcel(Parcel in) {
+ return new NotificationComponent(in);
+ }
+
+ @Override
+ public NotificationComponent[] newArray(int size) {
+ return new NotificationComponent[size];
+ }
+ };
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ NotificationType.INITIAL_ONGOING,
+ NotificationType.PREDEFERRED,
+ NotificationType.PREDEFERRED_PREPARING,
+ NotificationType.DEFERRED,
+ NotificationType.DEFERRED_ONGOING,
+ NotificationType.PORTAL
+ })
+ public @interface NotificationType {
+ int UNKNOWN = 0;
+ int INITIAL_ONGOING = 1;
+ int PREDEFERRED = 2;
+ int PREDEFERRED_PREPARING = 3;
+ int DEFERRED = 4;
+ int DEFERRED_ONGOING = 5;
+ int PORTAL = 6;
+ int MAX = 7;
+ }
+
+ public static class Builder {
+
+ private final NotificationComponent component;
+
+ public Builder(@NotificationType int notificationType) {
+ component = new NotificationComponent(notificationType);
+ }
+
+ public Builder putIntExtra(String key, int value) {
+ component.extraBundle.putInt(key, value);
+ return this;
+ }
+
+ public NotificationComponent build() {
+ return component;
+ }
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/portal/PortalConstants.java b/main/java/com/google/android/setupcompat/portal/PortalConstants.java
new file mode 100644
index 0000000..52d8700
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalConstants.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.StringDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Constant values used for Portal */
+public class PortalConstants {
+
+ /** Enumeration of pending reasons, for {@link IPortalProgressCallback#setPendingReason}. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ PendingReason.IN_PROGRESS,
+ PendingReason.PROGRESS_REQUEST_ANY_NETWORK,
+ PendingReason.PROGRESS_REQUEST_WIFI,
+ PendingReason.PROGRESS_REQUEST_MOBILE,
+ PendingReason.PROGRESS_RETRY,
+ PendingReason.PROGRESS_REQUEST_REMOVED,
+ PendingReason.MAX
+ })
+ public @interface PendingReason {
+ /**
+ * Don't used this, use {@link IPortalProgressCallback#setProgressCount} ot {@link
+ * IPortalProgressCallback#setProgressPercentage} will reset pending reason to in progress.
+ */
+ int IN_PROGRESS = 0;
+
+ /** Clients required network. */
+ int PROGRESS_REQUEST_ANY_NETWORK = 1;
+
+ /** Clients required a wifi network. */
+ int PROGRESS_REQUEST_WIFI = 2;
+
+ /** Client required a mobile data */
+ int PROGRESS_REQUEST_MOBILE = 3;
+
+ /** Client needs to wait for retry */
+ int PROGRESS_RETRY = 4;
+
+ /** Client required to remove added task */
+ int PROGRESS_REQUEST_REMOVED = 5;
+
+ int MAX = 6;
+ }
+
+ /** Bundle keys used in {@link IPortalProgressService#onGetRemainingValues}. */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({RemainingValues.REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB})
+ public @interface RemainingValues {
+ /** Remaining size to download in MB. */
+ String REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB = "RemainingSizeInKB";
+ }
+
+ private PortalConstants() {}
+}
diff --git a/main/java/com/google/android/setupcompat/portal/PortalHelper.java b/main/java/com/google/android/setupcompat/portal/PortalHelper.java
new file mode 100644
index 0000000..4d1965a
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalHelper.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import androidx.annotation.NonNull;
+import com.google.android.setupcompat.internal.Preconditions;
+import com.google.android.setupcompat.portal.PortalConstants.RemainingValues;
+import com.google.android.setupcompat.util.Logger;
+
+/** This class is responsible for safely executing methods on SetupNotificationService. */
+public class PortalHelper {
+
+ private static final Logger LOG = new Logger("PortalHelper");
+
+ public static final String EXTRA_KEY_IS_SETUP_WIZARD = "isSetupWizard";
+
+ public static final String ACTION_BIND_SETUP_NOTIFICATION_SERVICE =
+ "com.google.android.setupcompat.portal.SetupNotificationService.BIND";
+
+ public static final String RESULT_BUNDLE_KEY_RESULT = "Result";
+ public static final String RESULT_BUNDLE_KEY_ERROR = "Error";
+ public static final String RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE =
+ "PortalNotificationAvailable";
+
+ public static final Intent NOTIFICATION_SERVICE_INTENT =
+ new Intent(ACTION_BIND_SETUP_NOTIFICATION_SERVICE)
+ .setPackage("com.google.android.setupwizard");
+
+ /**
+ * Binds SetupNotificationService. For more detail see {@link Context#bindService(Intent,
+ * ServiceConnection, int)}
+ */
+ public static boolean bindSetupNotificationService(
+ @NonNull Context context, @NonNull ServiceConnection connection) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(connection, "ServiceConnection cannot be null");
+ try {
+ return context.bindService(NOTIFICATION_SERVICE_INTENT, connection, Context.BIND_AUTO_CREATE);
+ } catch (SecurityException e) {
+ LOG.e("Exception occurred while binding SetupNotificationService", e);
+ return false;
+ }
+ }
+
+ /**
+ * Registers a progress service to SUW service. The function response for bind service and invoke
+ * function safely, and returns the result using {@link RegisterCallback}.
+ *
+ * @param context The application context.
+ * @param component Identifies the progress service to execute.
+ * @param callback Receives register result. {@link RegisterCallback#onSuccess} called while
+ * register succeed. {@link RegisterCallback#onFailure} called while register failed.
+ */
+ public static void registerProgressService(
+ @NonNull Context context,
+ @NonNull ProgressServiceComponent component,
+ @NonNull RegisterCallback callback) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(component, "ProgressServiceComponent cannot be null");
+ Preconditions.checkNotNull(callback, "RegisterCallback cannot be null");
+
+ ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ if (binder != null) {
+ ISetupNotificationService service =
+ ISetupNotificationService.Stub.asInterface(binder);
+ try {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ final ServiceConnection serviceConnection = this;
+ service.registerProgressService(
+ component,
+ getCurrentUserHandle(),
+ new IPortalRegisterResultListener.Stub() {
+ @Override
+ public void onResult(Bundle result) {
+ if (result.getBoolean(RESULT_BUNDLE_KEY_RESULT, false)) {
+ callback.onSuccess(
+ result.getBoolean(
+ RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE, false));
+ } else {
+ callback.onFailure(
+ new IllegalStateException(
+ result.getString(RESULT_BUNDLE_KEY_ERROR, "Unknown error")));
+ }
+ context.unbindService(serviceConnection);
+ }
+ });
+ } else {
+ callback.onFailure(
+ new IllegalStateException(
+ "SetupNotificationService is not supported before Android N"));
+ context.unbindService(this);
+ }
+ } catch (RemoteException | NullPointerException e) {
+ callback.onFailure(e);
+ context.unbindService(this);
+ }
+ } else {
+ callback.onFailure(
+ new IllegalStateException("SetupNotification should not return null binder"));
+ context.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing when service disconnected
+ }
+ };
+
+ if (!bindSetupNotificationService(context, connection)) {
+ LOG.e("Failed to bind SetupNotificationService.");
+ callback.onFailure(new SecurityException("Failed to bind SetupNotificationService."));
+ }
+ }
+
+ public static void isPortalAvailable(
+ @NonNull Context context, @NonNull final PortalAvailableResultListener listener) {
+ ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ if (binder != null) {
+ ISetupNotificationService service =
+ ISetupNotificationService.Stub.asInterface(binder);
+
+ try {
+ listener.onResult(service.isPortalAvailable());
+ } catch (RemoteException e) {
+ LOG.e("Failed to invoke SetupNotificationService#isPortalAvailable");
+ listener.onResult(false);
+ }
+ }
+ context.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ if (!bindSetupNotificationService(context, connection)) {
+ LOG.e(
+ "Failed to bind SetupNotificationService. Do you have permission"
+ + " \"com.google.android.setupwizard.SETUP_PROGRESS_SERVICE\"");
+ listener.onResult(false);
+ }
+ }
+
+ public static void isProgressServiceAlive(
+ @NonNull final Context context,
+ @NonNull final ProgressServiceComponent component,
+ @NonNull final ProgressServiceAliveResultListener listener) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(component, "ProgressServiceComponent cannot be null");
+ Preconditions.checkNotNull(listener, "ProgressServiceAliveResultCallback cannot be null");
+
+ ServiceConnection connection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ if (binder != null) {
+ ISetupNotificationService service =
+ ISetupNotificationService.Stub.asInterface(binder);
+
+ try {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ listener.onResult(
+ service.isProgressServiceAlive(component, getCurrentUserHandle()));
+ } else {
+ listener.onResult(false);
+ }
+
+ } catch (RemoteException e) {
+ LOG.w("Failed to invoke SetupNotificationService#isProgressServiceAlive");
+ listener.onResult(false);
+ }
+ }
+ context.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ if (!bindSetupNotificationService(context, connection)) {
+ LOG.e(
+ "Failed to bind SetupNotificationService. Do you have permission"
+ + " \"com.google.android.setupwizard.SETUP_PROGRESS_SERVICE\"");
+ listener.onResult(false);
+ }
+ }
+
+ private static UserHandle getCurrentUserHandle() {
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ return UserHandle.getUserHandleForUid(Process.myUid());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates the {@code Bundle} including the bind progress service result.
+ *
+ * @param succeed whether bind service success or not.
+ * @param errorMsg describe the reason why bind service failed.
+ * @return A bundle include bind result and error message.
+ */
+ public static Bundle createResultBundle(
+ boolean succeed, String errorMsg, boolean isPortalNotificationAvailable) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, succeed);
+ if (!succeed) {
+ bundle.putString(RESULT_BUNDLE_KEY_ERROR, errorMsg);
+ }
+ bundle.putBoolean(
+ RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE, isPortalNotificationAvailable);
+ return bundle;
+ }
+
+ /**
+ * Returns {@code true}, if the intent is bound from SetupWizard, otherwise returns false.
+ *
+ * @param intent that received when onBind.
+ */
+ public static boolean isFromSUW(Intent intent) {
+ return intent != null && intent.getBooleanExtra(EXTRA_KEY_IS_SETUP_WIZARD, false);
+ }
+
+ /** A callback for accepting the results of SetupNotificationService. */
+ public interface RegisterCallback {
+ void onSuccess(boolean isPortalNow);
+
+ void onFailure(Throwable throwable);
+ }
+
+ public interface RegisterNotificationCallback {
+ void onSuccess();
+
+ void onFailure(Throwable throwable);
+ }
+
+ public interface ProgressServiceAliveResultListener {
+ void onResult(boolean isAlive);
+ }
+
+ public interface PortalAvailableResultListener {
+ void onResult(boolean isAvailable);
+ }
+
+ public static class RemainingValueBuilder {
+ private final Bundle bundle = new Bundle();
+
+ public static RemainingValueBuilder createBuilder() {
+ return new RemainingValueBuilder();
+ }
+
+ public RemainingValueBuilder setRemainingSizeInKB(int size) {
+ Preconditions.checkArgument(
+ size >= 0, "The remainingSize should be positive integer or zero.");
+ bundle.putInt(RemainingValues.REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB, size);
+ return this;
+ }
+
+ public Bundle build() {
+ return bundle;
+ }
+
+ private RemainingValueBuilder() {}
+ }
+
+ private PortalHelper() {}
+}
+
+
diff --git a/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java b/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java
new file mode 100644
index 0000000..cec2990
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.os.Bundle;
+
+public class PortalResultHelper {
+
+ public static final String RESULT_BUNDLE_KEY_RESULT = "Result";
+ public static final String RESULT_BUNDLE_KEY_ERROR = "Error";
+
+ public static boolean isSuccess(Bundle bundle) {
+ return bundle.getBoolean(RESULT_BUNDLE_KEY_RESULT, false);
+ }
+
+ public static String getErrorMessage(Bundle bundle) {
+ return bundle.getString(RESULT_BUNDLE_KEY_ERROR, null);
+ }
+
+ public static Bundle createSuccessBundle() {
+ Bundle resultBundle = new Bundle();
+ resultBundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, true);
+ return resultBundle;
+ }
+
+ public static Bundle createFailureBundle(String errorMessage) {
+ Bundle resultBundle = new Bundle();
+ resultBundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, false);
+ resultBundle.putString(RESULT_BUNDLE_KEY_ERROR, errorMessage);
+ return resultBundle;
+ }
+
+ private PortalResultHelper() {}
+ ;
+}
diff --git a/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java b/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java
new file mode 100644
index 0000000..be11239
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import com.google.android.setupcompat.internal.Preconditions;
+
+/**
+ * A class that represents how a progress service to be registered to {@link
+ * com.google.android.setupcompat.portal.ISetupNotificationService}.
+ */
+public class ProgressServiceComponent implements Parcelable {
+ private final String packageName;
+ private final String taskName;
+ private final boolean isSilent;
+ private final boolean autoRebind;
+ private final long timeoutForReRegister;
+ @StringRes private final int displayNameResId;
+ @DrawableRes private final int displayIconResId;
+ private final Intent serviceIntent;
+ private final Intent itemClickIntent;
+
+ private ProgressServiceComponent(
+ String packageName,
+ String taskName,
+ boolean isSilent,
+ boolean autoRebind,
+ long timeoutForReRegister,
+ @StringRes int displayNameResId,
+ @DrawableRes int displayIconResId,
+ Intent serviceIntent,
+ Intent itemClickIntent) {
+ this.packageName = packageName;
+ this.taskName = taskName;
+ this.isSilent = isSilent;
+ this.autoRebind = autoRebind;
+ this.timeoutForReRegister = timeoutForReRegister;
+ this.displayNameResId = displayNameResId;
+ this.displayIconResId = displayIconResId;
+ this.serviceIntent = serviceIntent;
+ this.itemClickIntent = itemClickIntent;
+ }
+
+ /** Returns a new instance of {@link Builder}. */
+ public static Builder newBuilder() {
+ return new ProgressServiceComponent.Builder();
+ }
+
+ /** Returns the package name where the service exist. */
+ @NonNull
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /** Returns the service class name */
+ @NonNull
+ public String getTaskName() {
+ return taskName;
+ }
+
+ /** Returns the whether the service is silent or not */
+ public boolean isSilent() {
+ return isSilent;
+ }
+
+ /** Auto rebind progress service while service connection disconnect. Default: true */
+ public boolean isAutoRebind() {
+ return autoRebind;
+ }
+
+ /** The timeout period waiting for client register progress service again. */
+ public long getTimeoutForReRegister() {
+ return timeoutForReRegister;
+ }
+
+ /** Returns the string resource id of display name. */
+ @StringRes
+ public int getDisplayName() {
+ return displayNameResId;
+ }
+
+ /** Returns the drawable resource id of display icon. */
+ @DrawableRes
+ public int getDisplayIcon() {
+ return displayIconResId;
+ }
+
+ /** Returns the Intent used to bind progress service. */
+ public Intent getServiceIntent() {
+ return serviceIntent;
+ }
+
+ /** Returns the Intent to start the user interface while progress item click. */
+ public Intent getItemClickIntent() {
+ return itemClickIntent;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getPackageName());
+ dest.writeString(getTaskName());
+ dest.writeInt(isSilent() ? 1 : 0);
+ dest.writeInt(getDisplayName());
+ dest.writeInt(getDisplayIcon());
+ dest.writeParcelable(getServiceIntent(), 0);
+ dest.writeParcelable(getItemClickIntent(), 0);
+ dest.writeInt(isAutoRebind() ? 1 : 0);
+ dest.writeLong(getTimeoutForReRegister());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ProgressServiceComponent> CREATOR =
+ new Creator<ProgressServiceComponent>() {
+ @Override
+ public ProgressServiceComponent createFromParcel(Parcel in) {
+ return ProgressServiceComponent.newBuilder()
+ .setPackageName(in.readString())
+ .setTaskName(in.readString())
+ .setSilentMode(in.readInt() == 1)
+ .setDisplayName(in.readInt())
+ .setDisplayIcon(in.readInt())
+ .setServiceIntent(in.readParcelable(Intent.class.getClassLoader()))
+ .setItemClickIntent(in.readParcelable(Intent.class.getClassLoader()))
+ .setAutoRebind(in.readInt() == 1)
+ .setTimeoutForReRegister(in.readLong())
+ .build();
+ }
+
+ @Override
+ public ProgressServiceComponent[] newArray(int size) {
+ return new ProgressServiceComponent[size];
+ }
+ };
+
+ /** Builder class for {@link ProgressServiceComponent} objects */
+ public static class Builder {
+ private String packageName;
+ private String taskName;
+ private boolean isSilent = false;
+ private boolean autoRebind = true;
+ private long timeoutForReRegister = 0L;
+ @StringRes private int displayNameResId;
+ @DrawableRes private int displayIconResId;
+ private Intent serviceIntent;
+ private Intent itemClickIntent;
+
+ /** Sets the packages name which is the service exists */
+ public Builder setPackageName(@NonNull String packageName) {
+ this.packageName = packageName;
+ return this;
+ }
+
+ /** Sets a name to identify what task this progress is. */
+ public Builder setTaskName(@NonNull String taskName) {
+ this.taskName = taskName;
+ return this;
+ }
+
+ /** Sets the service as silent mode, it executes without UI on PortalActivity. */
+ public Builder setSilentMode(boolean isSilent) {
+ this.isSilent = isSilent;
+ return this;
+ }
+
+ /** Sets the service need auto rebind or not when service connection disconnected. */
+ public Builder setAutoRebind(boolean autoRebind) {
+ this.autoRebind = autoRebind;
+ return this;
+ }
+
+ /**
+ * Sets the timeout period waiting for the client register again, only works when auto-rebind
+ * disabled. When 0 is set, will read default configuration from SUW.
+ */
+ public Builder setTimeoutForReRegister(long timeoutForReRegister) {
+ this.timeoutForReRegister = timeoutForReRegister;
+ return this;
+ }
+
+ /** Sets the name which is displayed on PortalActivity */
+ public Builder setDisplayName(@StringRes int displayNameResId) {
+ this.displayNameResId = displayNameResId;
+ return this;
+ }
+
+ /** Sets the icon which is display on PortalActivity */
+ public Builder setDisplayIcon(@DrawableRes int displayIconResId) {
+ this.displayIconResId = displayIconResId;
+ return this;
+ }
+
+ public Builder setServiceIntent(Intent serviceIntent) {
+ this.serviceIntent = serviceIntent;
+ return this;
+ }
+
+ public Builder setItemClickIntent(Intent itemClickIntent) {
+ this.itemClickIntent = itemClickIntent;
+ return this;
+ }
+
+ public ProgressServiceComponent build() {
+ Preconditions.checkNotNull(packageName, "packageName cannot be null.");
+ Preconditions.checkNotNull(taskName, "serviceClass cannot be null.");
+ Preconditions.checkNotNull(serviceIntent, "Service intent cannot be null.");
+ Preconditions.checkNotNull(itemClickIntent, "Item click intent cannot be null");
+ if (!isSilent) {
+ Preconditions.checkArgument(
+ displayNameResId != 0, "Invalidate resource id of display name");
+ Preconditions.checkArgument(
+ displayIconResId != 0, "Invalidate resource id of display icon");
+ }
+ return new ProgressServiceComponent(
+ packageName,
+ taskName,
+ isSilent,
+ autoRebind,
+ timeoutForReRegister,
+ displayNameResId,
+ displayIconResId,
+ serviceIntent,
+ itemClickIntent);
+ }
+
+ private Builder() {}
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/template/FooterActionButton.java b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
index bb26d19..86a06d9 100644
--- a/main/java/com/google/android/setupcompat/template/FooterActionButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
@@ -18,11 +18,11 @@ package com.google.android.setupcompat.template;
import android.annotation.SuppressLint;
import android.content.Context;
-import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
+import androidx.annotation.Nullable;
/** Button that can react to touch when disabled. */
public class FooterActionButton extends Button {
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index bc9e5c1..b75d972 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -24,16 +24,17 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.PersistableBundle;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
import androidx.annotation.AttrRes;
import androidx.annotation.CallSuper;
import androidx.annotation.ColorInt;
@@ -44,25 +45,15 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-import android.util.StateSet;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewStub;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
import com.google.android.setupcompat.PartnerCustomizationLayout;
import com.google.android.setupcompat.R;
import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
-import com.google.android.setupcompat.internal.Preconditions;
import com.google.android.setupcompat.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.Locale;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -77,6 +68,8 @@ public class FooterBarMixin implements Mixin {
@Nullable private final ViewStub footerStub;
@VisibleForTesting final boolean applyPartnerResources;
+ @VisibleForTesting final boolean applyDynamicColor;
+ @VisibleForTesting final boolean useFullDynamicColor;
private LinearLayout buttonContainer;
private FooterButton primaryButton;
@@ -94,8 +87,8 @@ public class FooterBarMixin implements Mixin {
@ColorInt private final int footerBarPrimaryBackgroundColor;
@ColorInt private final int footerBarSecondaryBackgroundColor;
private boolean removeFooterBarWhenEmpty = true;
+ private boolean isSecondaryButtonInPrimaryStyle = false;
- private static final float DEFAULT_DISABLED_ALPHA = 0.26f;
private static final AtomicInteger nextGeneratedId = new AtomicInteger(1);
@VisibleForTesting public final FooterBarMixinMetrics metrics = new FooterBarMixinMetrics();
@@ -110,10 +103,10 @@ public class FooterBarMixin implements Mixin {
Button button = buttonContainer.findViewById(id);
if (button != null) {
button.setEnabled(enabled);
- if (applyPartnerResources) {
- updateButtonTextColorWithPartnerConfig(
+ if (applyPartnerResources && !applyDynamicColor) {
+ updateButtonTextColorWithEnabledState(
button,
- (id == primaryButtonId)
+ (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
: PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR);
}
@@ -141,6 +134,25 @@ public class FooterBarMixin implements Mixin {
}
}
}
+
+ @Override
+ @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
+ public void onLocaleChanged(Locale locale) {
+ if (buttonContainer != null) {
+ Button button = buttonContainer.findViewById(id);
+ if (button != null && locale != null) {
+ button.setTextLocale(locale);
+ }
+ }
+ }
+
+ @Override
+ @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
+ public void onDirectionChanged(int direction) {
+ if (buttonContainer != null && direction != -1) {
+ buttonContainer.setLayoutDirection(direction);
+ }
+ }
};
}
@@ -159,6 +171,14 @@ public class FooterBarMixin implements Mixin {
layout instanceof PartnerCustomizationLayout
&& ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
+ applyDynamicColor =
+ layout instanceof PartnerCustomizationLayout
+ && ((PartnerCustomizationLayout) layout).shouldApplyDynamicColor();
+
+ useFullDynamicColor =
+ layout instanceof PartnerCustomizationLayout
+ && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
+
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.SucFooterBarMixin, defStyleAttr, 0);
defaultPadding =
@@ -253,11 +273,14 @@ public class FooterBarMixin implements Mixin {
return;
}
- @ColorInt
- int color =
- PartnerConfigHelper.get(context)
- .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR);
- buttonContainer.setBackgroundColor(color);
+ // skip apply partner resources on footerbar background if dynamic color enabled
+ if (!useFullDynamicColor) {
+ @ColorInt
+ int color =
+ PartnerConfigHelper.get(context)
+ .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR);
+ buttonContainer.setBackgroundColor(color);
+ }
footerBarPaddingTop =
(int)
@@ -273,6 +296,17 @@ public class FooterBarMixin implements Mixin {
footerBarPaddingTop,
buttonContainer.getPaddingRight(),
footerBarPaddingBottom);
+
+ if (PartnerConfigHelper.get(context)
+ .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT)) {
+ int minHeight =
+ (int)
+ PartnerConfigHelper.get(context)
+ .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT);
+ if (minHeight > 0) {
+ buttonContainer.setMinimumHeight(minHeight);
+ }
+ }
}
/**
@@ -310,7 +344,9 @@ public class FooterBarMixin implements Mixin {
.setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
.setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
.setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+ .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
.setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+ .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
.build();
FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
@@ -346,7 +382,14 @@ public class FooterBarMixin implements Mixin {
/** Sets secondary button for footer. */
@MainThread
public void setSecondaryButton(FooterButton footerButton) {
+ setSecondaryButton(footerButton, /*usePrimaryStyle= */ false);
+ }
+
+ /** Sets secondary button for footer. Allow to use the primary button style. */
+ @MainThread
+ public void setSecondaryButton(FooterButton footerButton, boolean usePrimaryStyle) {
ensureOnMainThread("setSecondaryButton");
+ isSecondaryButtonInPrimaryStyle = usePrimaryStyle;
ensureFooterInflated();
// Setup button partner config
@@ -355,18 +398,29 @@ public class FooterBarMixin implements Mixin {
.setPartnerTheme(
getPartnerTheme(
footerButton,
- /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Secondary,
- /* buttonBackgroundColorConfig= */ PartnerConfig
- .CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR))
- .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
+ /* defaultPartnerTheme= */ usePrimaryStyle
+ ? R.style.SucPartnerCustomizationButton_Primary
+ : R.style.SucPartnerCustomizationButton_Secondary,
+ /* buttonBackgroundColorConfig= */ usePrimaryStyle
+ ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR
+ : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR))
+ .setButtonBackgroundConfig(
+ usePrimaryStyle
+ ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR
+ : 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)
- .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
+ .setTextColorConfig(
+ usePrimaryStyle
+ ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
+ : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
.setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+ .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
.setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+ .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
.build();
FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
@@ -395,6 +449,16 @@ public class FooterBarMixin implements Mixin {
buttonContainer.removeAllViews();
if (tempSecondaryButton != null) {
+ if (isSecondaryButtonInPrimaryStyle) {
+ // Since the secondary button has the same style (with background) as the primary button,
+ // we need to have the left padding equal to the right padding.
+ updateFooterBarPadding(
+ buttonContainer,
+ buttonContainer.getPaddingRight(),
+ buttonContainer.getPaddingTop(),
+ buttonContainer.getPaddingRight(),
+ buttonContainer.getPaddingBottom());
+ }
buttonContainer.addView(tempSecondaryButton);
}
addSpace();
@@ -411,7 +475,7 @@ public class FooterBarMixin implements Mixin {
protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) {
// Try to set default background
if (defaultButtonBackgroundColor != 0) {
- updateButtonBackground(button, defaultButtonBackgroundColor);
+ FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor);
} else {
// TODO: get button background color from activity theme
}
@@ -544,187 +608,30 @@ public class FooterBarMixin implements Mixin {
if (!applyPartnerResources) {
return;
}
- updateButtonTextColorWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextColorConfig());
- updateButtonTextSizeWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextSizeConfig());
- updateButtonTypeFaceWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextTypeFaceConfig());
- updateButtonBackgroundWithPartnerConfig(
+ FooterButtonStyleUtils.applyButtonPartnerResources(
+ context,
button,
- footerButtonPartnerConfig.getButtonBackgroundConfig(),
- footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
- footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
- updateButtonRadiusWithPartnerConfig(button, footerButtonPartnerConfig.getButtonRadiusConfig());
- updateButtonIconWithPartnerConfig(button, footerButtonPartnerConfig.getButtonIconConfig());
- updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig);
+ applyDynamicColor,
+ /* isButtonIconAtEnd= */ (button.getId() == primaryButtonId),
+ footerButtonPartnerConfig);
+ if (!applyDynamicColor) {
+ // adjust text color based on enabled state
+ updateButtonTextColorWithEnabledState(
+ button, footerButtonPartnerConfig.getButtonTextColorConfig());
+ }
}
- private void updateButtonTextColorWithPartnerConfig(
+ private void updateButtonTextColorWithEnabledState(
Button button, PartnerConfig buttonTextColorConfig) {
if (button.isEnabled()) {
- @ColorInt
- int color = PartnerConfigHelper.get(context).getColor(context, buttonTextColorConfig);
- if (color != Color.TRANSPARENT) {
- button.setTextColor(ColorStateList.valueOf(color));
- }
+ FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
+ context, button, buttonTextColorConfig);
} else {
- // disable state will use the default disable state color
- button.setTextColor(
- button.getId() == primaryButtonId ? primaryDefaultTextColor : secondaryDefaultTextColor);
- }
- }
-
- private void updateButtonTextSizeWithPartnerConfig(
- Button button, PartnerConfig buttonTextSizeConfig) {
- float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig);
- if (size > 0) {
- button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
- }
- }
-
- private void updateButtonTypeFaceWithPartnerConfig(
- Button button, PartnerConfig buttonTextTypeFaceConfig) {
- String fontFamilyName =
- PartnerConfigHelper.get(context).getString(context, buttonTextTypeFaceConfig);
- Typeface font = Typeface.create(fontFamilyName, Typeface.NORMAL);
- if (font != null) {
- button.setTypeface(font);
- }
- }
-
- @TargetApi(VERSION_CODES.Q)
- private void updateButtonBackgroundWithPartnerConfig(
- 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) {
- 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(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
- // parent is getting the correct drawable state from the view, the children won't get those
- // states until a state change happens.
- // As a workaround, we mutate the drawable and forcibly set the state to empty, and then
- // refresh the state so the children will have the updated states.
- button.getBackground().mutate().setState(new int[0]);
- button.refreshDrawableState();
- button.setBackgroundTintList(colorStateList);
- }
- }
-
- private void updateButtonBackground(Button button, @ColorInt int color) {
- button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP);
- }
-
- private void updateButtonRadiusWithPartnerConfig(
- Button button, PartnerConfig buttonRadiusConfig) {
- if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
- float radius = PartnerConfigHelper.get(context).getDimension(context, buttonRadiusConfig);
- GradientDrawable gradientDrawable = getGradientDrawable(button);
- if (gradientDrawable != null) {
- gradientDrawable.setCornerRadius(radius);
- }
- }
- }
-
- private void updateButtonRippleColorWithPartnerConfig(
- Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) {
- // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
- // unavailable. Since Stencil customization provider only works on Q+, there is no need to
- // perform any customization for versions 21.
- if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- RippleDrawable rippleDrawable = getRippleDrawable(button);
- if (rippleDrawable == null) {
- return;
- }
-
- int[] pressedState = {android.R.attr.state_pressed};
- @ColorInt int color;
- // Get partner text color.
- color =
- PartnerConfigHelper.get(context)
- .getColor(context, footerButtonPartnerConfig.getButtonTextColorConfig());
-
- float alpha =
- PartnerConfigHelper.get(context)
- .getFraction(context, footerButtonPartnerConfig.getButtonRippleColorAlphaConfig());
-
- // Set text color for ripple.
- ColorStateList colorStateList =
- new ColorStateList(
- new int[][] {pressedState, StateSet.NOTHING},
- new int[] {convertRgbToArgb(color, alpha), Color.TRANSPARENT});
- rippleDrawable.setColor(colorStateList);
- }
- }
-
- private void updateButtonIconWithPartnerConfig(Button button, PartnerConfig buttonIconConfig) {
- if (button == null) {
- return;
- }
- Drawable icon = null;
- if (buttonIconConfig != null) {
- icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig);
- }
- setButtonIcon(button, icon);
- }
-
- private void setButtonIcon(Button button, Drawable icon) {
- if (button == null) {
- return;
- }
-
- if (icon != null) {
- // TODO: restrict the icons to a reasonable size
- int h = icon.getIntrinsicHeight();
- int w = icon.getIntrinsicWidth();
- icon.setBounds(0, 0, w, h);
- }
-
- Drawable iconStart = null;
- Drawable iconEnd = null;
- if (button.getId() == primaryButtonId) {
- iconEnd = icon;
- } else if (button.getId() == secondaryButtonId) {
- iconStart = icon;
- }
- if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
- button.setCompoundDrawablesRelative(iconStart, null, iconEnd, null);
- } else {
- button.setCompoundDrawables(iconStart, null, iconEnd, null);
+ FooterButtonStyleUtils.updateButtonTextDisableColor(
+ button,
+ /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle)
+ ? primaryDefaultTextColor
+ : secondaryDefaultTextColor);
}
}
@@ -763,43 +670,6 @@ public class FooterBarMixin implements Mixin {
return result;
}
- GradientDrawable getGradientDrawable(Button button) {
- // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
- // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
- // works on Q+, there is no need to perform any customization for versions 21.
- if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- Drawable drawable = button.getBackground();
- if (drawable instanceof InsetDrawable) {
- LayerDrawable layerDrawable = (LayerDrawable) ((InsetDrawable) drawable).getDrawable();
- return (GradientDrawable) layerDrawable.getDrawable(0);
- } else if (drawable instanceof RippleDrawable) {
- InsetDrawable insetDrawable = (InsetDrawable) ((RippleDrawable) drawable).getDrawable(0);
- return (GradientDrawable) insetDrawable.getDrawable();
- }
- }
- return null;
- }
-
- RippleDrawable getRippleDrawable(Button button) {
- // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
- // unavailable. Since Stencil customization provider only works on Q+, there is no need to
- // perform any customization for versions 21.
- if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
- Drawable drawable = button.getBackground();
- if (drawable instanceof InsetDrawable) {
- return (RippleDrawable) ((InsetDrawable) drawable).getDrawable();
- } else if (drawable instanceof RippleDrawable) {
- return (RippleDrawable) drawable;
- }
- }
- return null;
- }
-
- @ColorInt
- private static int convertRgbToArgb(@ColorInt int color, float alpha) {
- return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color));
- }
-
protected View inflateFooter(@LayoutRes int footer) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
LayoutInflater inflater =
diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java
index 2fa8c7c..90c13ec 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButton.java
@@ -23,17 +23,18 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build.VERSION_CODES;
import android.os.PersistableBundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
-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;
+import java.util.Locale;
/**
* Definition of a footer button. Clients can use this class to customize attributes like text,
@@ -53,6 +54,8 @@ public final class FooterButton implements OnClickListener {
private OnClickListener onClickListenerWhenDisabled;
private OnButtonEventListener buttonListener;
private int clickCount = 0;
+ private Locale locale;
+ private int direction;
public FooterButton(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SucFooterButton);
@@ -78,11 +81,15 @@ public final class FooterButton implements OnClickListener {
CharSequence text,
@Nullable OnClickListener listener,
@ButtonType int buttonType,
- @StyleRes int theme) {
+ @StyleRes int theme,
+ Locale locale,
+ int direction) {
this.text = text;
onClickListener = listener;
this.buttonType = buttonType;
this.theme = theme;
+ this.locale = locale;
+ this.direction = direction;
}
/** Returns the text that this footer button is displaying. */
@@ -142,6 +149,16 @@ public final class FooterButton implements OnClickListener {
return enabled;
}
+ /** Returns the layout direction for this footer button. */
+ public int getLayoutDirection() {
+ return direction;
+ }
+
+ /** Returns the text locale for this footer button. */
+ public Locale getTextLocale() {
+ return locale;
+ }
+
/**
* Sets the visibility state of this footer button.
*
@@ -172,6 +189,22 @@ public final class FooterButton implements OnClickListener {
}
}
+ /** Sets the text locale to be displayed on footer button. */
+ public void setTextLocale(Locale locale) {
+ this.locale = locale;
+ if (buttonListener != null) {
+ buttonListener.onLocaleChanged(locale);
+ }
+ }
+
+ /** Sets the layout direction to be displayed on footer button. */
+ public void setLayoutDirection(int direction) {
+ this.direction = direction;
+ if (buttonListener != null) {
+ buttonListener.onDirectionChanged(direction);
+ }
+ }
+
/**
* Registers a callback to be invoked when footer button API has set.
*
@@ -201,6 +234,10 @@ public final class FooterButton implements OnClickListener {
void onVisibilityChanged(int visibility);
void onTextChanged(CharSequence text);
+
+ void onLocaleChanged(Locale locale);
+
+ void onDirectionChanged(int direction);
}
/** Maximum valid value of ButtonType */
@@ -308,12 +345,16 @@ public final class FooterButton implements OnClickListener {
* .setListener(primaryButton)
* .setButtonType(ButtonType.NEXT)
* .setTheme(R.style.SuwGlifButton_Primary)
+ * .setTextLocale(Locale.CANADA)
+ * .setLayoutDirection(View.LAYOUT_DIRECTION_LTR)
* .build();
* </pre>
*/
public static class Builder {
private final Context context;
private String text = "";
+ private Locale locale = null;
+ private int direction = -1;
private OnClickListener onClickListener = null;
@ButtonType private int buttonType = ButtonType.OTHER;
private int theme = 0;
@@ -334,6 +375,18 @@ public final class FooterButton implements OnClickListener {
return this;
}
+ /** Sets the {@code locale} of FooterButton. */
+ public Builder setTextLocale(Locale locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ /** Sets the {@code direction} of FooterButton. */
+ public Builder setLayoutDirection(int direction) {
+ this.direction = direction;
+ return this;
+ }
+
/** Sets the {@code listener} of FooterButton. */
public Builder setListener(@Nullable OnClickListener listener) {
onClickListener = listener;
@@ -353,7 +406,7 @@ public final class FooterButton implements OnClickListener {
}
public FooterButton build() {
- return new FooterButton(text, onClickListener, buttonType, theme);
+ return new FooterButton(text, onClickListener, buttonType, theme, locale, direction);
}
}
}
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java b/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
index fe2538b..10aa052 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
@@ -19,10 +19,10 @@ package com.google.android.setupcompat.template;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.InflateException;
+import androidx.annotation.NonNull;
import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
new file mode 100644
index 0000000..ef45b5c
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.template;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.widget.Button;
+import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.R;
+import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
+import com.google.android.setupcompat.internal.Preconditions;
+import com.google.android.setupcompat.partnerconfig.PartnerConfig;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+
+/** Utils for updating the button style. */
+public class FooterButtonStyleUtils {
+ private static final float DEFAULT_DISABLED_ALPHA = 0.26f;
+
+ /** Apply the partner primary button style to given {@code button}. */
+ public static void applyPrimaryButtonPartnerResource(
+ Context context, Button button, boolean applyDynamicColor) {
+
+ FooterButtonPartnerConfig footerButtonPartnerConfig =
+ new FooterButtonPartnerConfig.Builder(null)
+ .setPartnerTheme(R.style.SucPartnerCustomizationButton_Primary)
+ .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
+ .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
+ .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+ .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
+ .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
+ .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
+ .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+ .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
+ .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+ .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
+ .build();
+ applyButtonPartnerResources(
+ context,
+ button,
+ applyDynamicColor,
+ /* isButtonIconAtEnd= */ true,
+ footerButtonPartnerConfig);
+ }
+
+ /** Apply the partner secondary button style to given {@code button}. */
+ public static void applySecondaryButtonPartnerResource(
+ Context context, Button button, boolean applyDynamicColor) {
+
+ int defaultTheme = R.style.SucPartnerCustomizationButton_Secondary;
+ int color =
+ PartnerConfigHelper.get(context)
+ .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR);
+ if (color != Color.TRANSPARENT) {
+ defaultTheme = R.style.SucPartnerCustomizationButton_Primary;
+ }
+ // Setup button partner config
+ FooterButtonPartnerConfig footerButtonPartnerConfig =
+ new FooterButtonPartnerConfig.Builder(null)
+ .setPartnerTheme(defaultTheme)
+ .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
+ .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
+ .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+ .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
+ .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
+ .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
+ .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+ .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
+ .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+ .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
+ .build();
+ applyButtonPartnerResources(
+ context,
+ button,
+ applyDynamicColor,
+ /* isButtonIconAtEnd= */ false,
+ footerButtonPartnerConfig);
+ }
+
+ static void applyButtonPartnerResources(
+ Context context,
+ Button button,
+ boolean applyDynamicColor,
+ boolean isButtonIconAtEnd,
+ FooterButtonPartnerConfig footerButtonPartnerConfig) {
+
+ // If dynamic color enabled, these colors won't be overrode by partner config.
+ // Instead, these colors align with the current theme colors.
+ if (!applyDynamicColor) {
+ // use default disable color util we support the partner disable text color
+ if (button.isEnabled()) {
+ FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonTextColorConfig());
+ }
+ FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
+ context,
+ button,
+ footerButtonPartnerConfig.getButtonBackgroundConfig(),
+ footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
+ footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
+ }
+ FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig(
+ context,
+ button,
+ applyDynamicColor,
+ footerButtonPartnerConfig.getButtonTextColorConfig(),
+ footerButtonPartnerConfig.getButtonRippleColorAlphaConfig());
+ FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonTextSizeConfig());
+ FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonMinHeightConfig());
+ FooterButtonStyleUtils.updateButtonTypeFaceWithPartnerConfig(
+ context,
+ button,
+ footerButtonPartnerConfig.getButtonTextTypeFaceConfig(),
+ footerButtonPartnerConfig.getButtonTextStyleConfig());
+ FooterButtonStyleUtils.updateButtonRadiusWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonRadiusConfig());
+ FooterButtonStyleUtils.updateButtonIconWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonIconConfig(), isButtonIconAtEnd);
+ }
+
+ static void updateButtonTextEnabledColorWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonEnableTextColorConfig) {
+ @ColorInt
+ int color = PartnerConfigHelper.get(context).getColor(context, buttonEnableTextColorConfig);
+ updateButtonTextEnabledColor(button, color);
+ }
+
+ static void updateButtonTextEnabledColor(Button button, @ColorInt int textColor) {
+ if (textColor != Color.TRANSPARENT) {
+ button.setTextColor(ColorStateList.valueOf(textColor));
+ }
+ }
+
+ static void updateButtonTextDisableColor(Button button, ColorStateList disabledTextColor) {
+ // TODO : add disable footer button text color partner config
+
+ // disable state will use the default disable state color
+ button.setTextColor(disabledTextColor);
+ }
+
+ @TargetApi(VERSION_CODES.Q)
+ static void updateButtonBackgroundWithPartnerConfig(
+ Context context,
+ 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 = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundConfig);
+ float disabledAlpha =
+ PartnerConfigHelper.get(context).getFraction(context, buttonDisableAlphaConfig, 0f);
+ @ColorInt
+ int disabledColor =
+ PartnerConfigHelper.get(context).getColor(context, buttonDisableBackgroundConfig);
+
+ updateButtonBackgroundTintList(context, button, color, disabledAlpha, disabledColor);
+ }
+
+ @TargetApi(VERSION_CODES.Q)
+ static void updateButtonBackgroundTintList(
+ Context context,
+ Button button,
+ @ColorInt int color,
+ float disabledAlpha,
+ @ColorInt int disabledColor) {
+ int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
+ int[] ENABLED_STATE_SET = {};
+
+ if (color != Color.TRANSPARENT) {
+ if (disabledAlpha <= 0f) {
+ // if no partner resource, fallback to theme disable alpha
+ TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha});
+ float 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(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
+ // parent is getting the correct drawable state from the view, the children won't get those
+ // states until a state change happens.
+ // As a workaround, we mutate the drawable and forcibly set the state to empty, and then
+ // refresh the state so the children will have the updated states.
+ button.getBackground().mutate().setState(new int[0]);
+ button.refreshDrawableState();
+ button.setBackgroundTintList(colorStateList);
+ }
+ }
+
+ @TargetApi(VERSION_CODES.Q)
+ static void updateButtonRippleColorWithPartnerConfig(
+ Context context,
+ Button button,
+ boolean applyDynamicColor,
+ PartnerConfig buttonTextColorConfig,
+ PartnerConfig buttonRippleColorAlphaConfig) {
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+
+ @ColorInt int textDefaultColor;
+ if (applyDynamicColor) {
+ // Get dynamic text color
+ textDefaultColor = button.getTextColors().getDefaultColor();
+ } else {
+ // Get partner text color.
+ textDefaultColor =
+ PartnerConfigHelper.get(context).getColor(context, buttonTextColorConfig);
+ }
+ float alpha =
+ PartnerConfigHelper.get(context).getFraction(context, buttonRippleColorAlphaConfig);
+ updateButtonRippleColor(button, textDefaultColor, alpha);
+ }
+ }
+
+ private static void updateButtonRippleColor(
+ Button button, @ColorInt int textColor, float rippleAlpha) {
+ // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
+ // unavailable. Since Stencil customization provider only works on Q+, there is no need to
+ // perform any customization for versions 21.
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ RippleDrawable rippleDrawable = getRippleDrawable(button);
+ if (rippleDrawable == null) {
+ return;
+ }
+
+ int[] pressedState = {android.R.attr.state_pressed};
+
+ // Set text color for ripple.
+ ColorStateList colorStateList =
+ new ColorStateList(
+ new int[][] {pressedState, StateSet.NOTHING},
+ new int[] {convertRgbToArgb(textColor, rippleAlpha), Color.TRANSPARENT});
+ rippleDrawable.setColor(colorStateList);
+ }
+ }
+
+ static void updateButtonTextSizeWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonTextSizeConfig) {
+ float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig);
+ if (size > 0) {
+ button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+ }
+ }
+
+ static void updateButtonMinHeightWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonMinHeightConfig) {
+ if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonMinHeightConfig)) {
+ float size = PartnerConfigHelper.get(context).getDimension(context, buttonMinHeightConfig);
+ if (size > 0) {
+ button.setMinHeight((int) size);
+ }
+ }
+ }
+
+ static void updateButtonTypeFaceWithPartnerConfig(
+ Context context,
+ Button button,
+ PartnerConfig buttonTextTypeFaceConfig,
+ PartnerConfig buttonTextStyleConfig) {
+ String fontFamilyName =
+ PartnerConfigHelper.get(context).getString(context, buttonTextTypeFaceConfig);
+
+ int textStyleValue = Typeface.NORMAL;
+ if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonTextStyleConfig)) {
+ textStyleValue =
+ PartnerConfigHelper.get(context)
+ .getInteger(context, buttonTextStyleConfig, Typeface.NORMAL);
+ }
+ Typeface font = Typeface.create(fontFamilyName, textStyleValue);
+ if (font != null) {
+ button.setTypeface(font);
+ }
+ }
+
+ static void updateButtonRadiusWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonRadiusConfig) {
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
+ float radius = PartnerConfigHelper.get(context).getDimension(context, buttonRadiusConfig);
+ GradientDrawable gradientDrawable = getGradientDrawable(button);
+ if (gradientDrawable != null) {
+ gradientDrawable.setCornerRadius(radius);
+ }
+ }
+ }
+
+ static void updateButtonIconWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonIconConfig, boolean isButtonIconAtEnd) {
+ if (button == null) {
+ return;
+ }
+ Drawable icon = null;
+ if (buttonIconConfig != null) {
+ icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig);
+ }
+ setButtonIcon(button, icon, isButtonIconAtEnd);
+ }
+
+ private static void setButtonIcon(Button button, Drawable icon, boolean isButtonIconAtEnd) {
+ if (button == null) {
+ return;
+ }
+
+ if (icon != null) {
+ // TODO: restrict the icons to a reasonable size
+ int h = icon.getIntrinsicHeight();
+ int w = icon.getIntrinsicWidth();
+ icon.setBounds(0, 0, w, h);
+ }
+
+ Drawable iconStart = null;
+ Drawable iconEnd = null;
+ if (isButtonIconAtEnd) {
+ iconEnd = icon;
+ } else {
+ iconStart = icon;
+ }
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
+ button.setCompoundDrawablesRelative(iconStart, null, iconEnd, null);
+ } else {
+ button.setCompoundDrawables(iconStart, null, iconEnd, null);
+ }
+ }
+
+ static void updateButtonBackground(Button button, @ColorInt int color) {
+ button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP);
+ }
+
+ @VisibleForTesting
+ public static GradientDrawable getGradientDrawable(Button button) {
+ // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
+ // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
+ // works on Q+, there is no need to perform any customization for versions 21.
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ Drawable drawable = button.getBackground();
+ if (drawable instanceof InsetDrawable) {
+ LayerDrawable layerDrawable = (LayerDrawable) ((InsetDrawable) drawable).getDrawable();
+ return (GradientDrawable) layerDrawable.getDrawable(0);
+ } else if (drawable instanceof RippleDrawable) {
+ if (((RippleDrawable) drawable).getDrawable(0) instanceof GradientDrawable) {
+ return (GradientDrawable) ((RippleDrawable) drawable).getDrawable(0);
+ }
+ InsetDrawable insetDrawable = (InsetDrawable) ((RippleDrawable) drawable).getDrawable(0);
+ return (GradientDrawable) insetDrawable.getDrawable();
+ }
+ }
+ return null;
+ }
+
+ static RippleDrawable getRippleDrawable(Button button) {
+ // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
+ // unavailable. Since Stencil customization provider only works on Q+, there is no need to
+ // perform any customization for versions 21.
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ Drawable drawable = button.getBackground();
+ if (drawable instanceof InsetDrawable) {
+ return (RippleDrawable) ((InsetDrawable) drawable).getDrawable();
+ } else if (drawable instanceof RippleDrawable) {
+ return (RippleDrawable) drawable;
+ }
+ }
+ return null;
+ }
+
+ @ColorInt
+ private static int convertRgbToArgb(@ColorInt int color, float alpha) {
+ return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ private FooterButtonStyleUtils() {}
+}
diff --git a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
index 1bd6949..c0f1c45 100644
--- a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
@@ -25,13 +25,13 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.AttrRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.google.android.setupcompat.PartnerCustomizationLayout;
import com.google.android.setupcompat.R;
import com.google.android.setupcompat.partnerconfig.PartnerConfig;
@@ -112,10 +112,14 @@ public class StatusBarMixin implements Mixin {
*/
public void setStatusBarBackground(Drawable background) {
if (partnerCustomizationLayout.shouldApplyPartnerResource()) {
+ // If full dynamic color enabled which means this activity is running outside of setup
+ // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
+ if (!partnerCustomizationLayout.useFullDynamicColor()) {
Context context = partnerCustomizationLayout.getContext();
background =
PartnerConfigHelper.get(context)
.getDrawable(context, PartnerConfig.CONFIG_STATUS_BAR_BACKGROUND);
+ }
}
if (statusBarLayout == null) {
diff --git a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
index e055d28..32c708c 100644
--- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
@@ -24,13 +24,13 @@ import android.graphics.Color;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.Window;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-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;
@@ -47,6 +47,7 @@ public class SystemNavBarMixin implements Mixin {
private final TemplateLayout templateLayout;
@Nullable private final Window windowOfActivity;
@VisibleForTesting final boolean applyPartnerResources;
+ @VisibleForTesting final boolean useFullDynamicColor;
private int sucSystemNavBarBackgroundColor = 0;
/**
@@ -61,6 +62,10 @@ public class SystemNavBarMixin implements Mixin {
this.applyPartnerResources =
layout instanceof PartnerCustomizationLayout
&& ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
+
+ this.useFullDynamicColor =
+ layout instanceof PartnerCustomizationLayout
+ && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
}
/**
@@ -83,6 +88,19 @@ public class SystemNavBarMixin implements Mixin {
setLightSystemNavBar(
a.getBoolean(
R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar()));
+
+ // Support updating system navigation bar divider color from P.
+ if (VERSION.SDK_INT >= VERSION_CODES.P) {
+ // get fallback value from theme
+ int[] navBarDividerColorAttr = new int[] {android.R.attr.navigationBarDividerColor};
+ TypedArray typedArray =
+ templateLayout.getContext().obtainStyledAttributes(navBarDividerColorAttr);
+ int defaultColor = typedArray.getColor(/* index= */ 0, /* defValue= */ 0);
+ int sucSystemNavBarDividerColor =
+ a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarDividerColor, defaultColor);
+ setSystemNavBarDividerColor(sucSystemNavBarDividerColor);
+ typedArray.recycle();
+ }
a.recycle();
}
}
@@ -96,10 +114,14 @@ public class SystemNavBarMixin implements Mixin {
public void setSystemNavBarBackground(int color) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) {
if (applyPartnerResources) {
- Context context = templateLayout.getContext();
- color =
- PartnerConfigHelper.get(context)
- .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
+ // If full dynamic color enabled which means this activity is running outside of setup
+ // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
+ if (!useFullDynamicColor) {
+ Context context = templateLayout.getContext();
+ color =
+ PartnerConfigHelper.get(context)
+ .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
+ }
}
windowOfActivity.setNavigationBarColor(color);
}
@@ -120,6 +142,7 @@ 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 && windowOfActivity != null) {
if (applyPartnerResources) {
@@ -158,6 +181,28 @@ public class SystemNavBarMixin implements Mixin {
}
/**
+ * Sets the divider color of navigation bar. The color will be overridden by partner resource if
+ * the activity is running in setup wizard flow.
+ *
+ * @param color the default divider color of navigation bar
+ */
+ public void setSystemNavBarDividerColor(int color) {
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.P && windowOfActivity != null) {
+ if (applyPartnerResources) {
+ Context context = templateLayout.getContext();
+ // Do nothing if the old version partner provider did not contain the new config.
+ if (PartnerConfigHelper.get(context)
+ .isPartnerConfigAvailable(PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR)) {
+ color =
+ PartnerConfigHelper.get(context)
+ .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR);
+ }
+ }
+ windowOfActivity.setNavigationBarDividerColor(color);
+ }
+ }
+
+ /**
* 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
diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
new file mode 100644
index 0000000..ea54745
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.util;
+
+import android.os.Build;
+
+/**
+ * An util class to check whether the current OS version is higher or equal to sdk version of
+ * device.
+ */
+public final class BuildCompatUtils {
+
+ /**
+ * Implementation of BuildCompat.isAtLeast*() suitable for use in Setup
+ *
+ * <p>BuildCompat.isAtLeast*() can be changed by Android Release team, and once that is changed it
+ * may take weeks for that to propagate to stable/prerelease/experimental SDKs in Google3. Also it
+ * can be different in all these channels. This can cause random issues, especially with sidecars
+ * (i.e., the code running on R may not know that it runs on R).
+ *
+ * <p>This still should try using BuildCompat.isAtLeastR() as source of truth, but also checking
+ * for VERSION_SDK_INT and VERSION.CODENAME in case when BuildCompat implementation returned
+ * false. Note that both checks should be >= and not = to make sure that when Android version
+ * increases (i.e., from R to S), this does not stop working.
+ *
+ * <p>Supported configurations:
+ *
+ * <ul>
+ * <li>For current Android release: while new API is not finalized yet (CODENAME = "S", SDK_INT
+ * = 30|31)
+ * <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 31)
+ * <li>For next Android release (CODENAME = "T", SDK_INT = 30+)
+ * </ul>
+ *
+ * <p>Note that Build.VERSION_CODES.S cannot be used here until final SDK is available in all
+ * Google3 channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API
+ * finalization.
+ *
+ * @return Whether the current OS version is higher or equal to S.
+ */
+ public static boolean isAtLeastS() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return false;
+ }
+ return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 31)
+ || (Build.VERSION.CODENAME.length() == 1
+ && Build.VERSION.CODENAME.charAt(0) >= 'S'
+ && Build.VERSION.CODENAME.charAt(0) <= 'Z');
+ }
+
+ private BuildCompatUtils() {}
+}
diff --git a/main/java/com/google/android/setupcompat/util/Logger.java b/main/java/com/google/android/setupcompat/util/Logger.java
new file mode 100644
index 0000000..3f8dfd1
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/util/Logger.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.setupcompat.util;
+
+import android.util.Log;
+
+/**
+ * Helper class that wraps {@link Log} to log messages to logcat. This class consolidate the log
+ * {@link #TAG} in both SetupCompat and SetupDesign library.
+ *
+ * <p>When logging verbose and debug logs, the logs should either be guarded by {@code if
+ * (logger.isV())}, or a constant if (DEBUG). That DEBUG constant should be false on any submitted
+ * code.
+ */
+public final class Logger {
+
+ public static final String TAG = "SetupLibrary";
+
+ private final String prefix;
+
+ public Logger(Class<?> cls) {
+ this(cls.getSimpleName());
+ }
+
+ public Logger(String prefix) {
+ this.prefix = "[" + prefix + "] ";
+ }
+
+ public boolean isV() {
+ return Log.isLoggable(TAG, Log.VERBOSE);
+ }
+
+ public boolean isD() {
+ return Log.isLoggable(TAG, Log.DEBUG);
+ }
+
+ public boolean isI() {
+ return Log.isLoggable(TAG, Log.INFO);
+ }
+
+ public void atVerbose(String message) {
+ if (isV()) {
+ Log.v(TAG, prefix.concat(message));
+ }
+ }
+
+ public void atDebug(String message) {
+ if (isD()) {
+ Log.d(TAG, prefix.concat(message));
+ }
+ }
+
+ public void atInfo(String message) {
+ if (isI()) {
+ Log.i(TAG, prefix.concat(message));
+ }
+ }
+
+ public void w(String message) {
+ Log.w(TAG, prefix.concat(message));
+ }
+
+ public void e(String message) {
+ Log.e(TAG, prefix.concat(message));
+ }
+
+ public void e(String message, Throwable throwable) {
+ Log.e(TAG, prefix.concat(message), throwable);
+ }
+}
diff --git a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
index 75e5dd3..dd92501 100644
--- a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
+++ b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
@@ -24,13 +24,12 @@ import android.content.res.TypedArray;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
-import androidx.annotation.RequiresPermission;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
+import androidx.annotation.RequiresPermission;
/**
* A helper class to manage the system navigation bar and status bar. This will add various
@@ -44,7 +43,7 @@ import android.view.WindowManager;
*/
public final class SystemBarHelper {
- private static final String TAG = "SystemBarHelper";
+ private static final Logger LOG = new Logger("SystemBarHelper");
/** Needs to be equal to View.STATUS_BAR_DISABLE_BACK */
private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
@@ -329,7 +328,7 @@ public final class SystemBarHelper {
// 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);
+ LOG.e("Cannot get decor view of window: " + window);
}
}
}
diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
index bfe1dbb..79976bc 100644
--- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
+++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
@@ -34,7 +34,7 @@ import java.util.Arrays;
*/
public final class WizardManagerHelper {
- private static final String ACTION_NEXT = "com.android.wizard.NEXT";
+ @VisibleForTesting public static final String ACTION_NEXT = "com.android.wizard.NEXT";
// EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are
// kept for backwards compatibility.
@@ -43,10 +43,27 @@ public final class WizardManagerHelper {
@VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle";
private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
- @VisibleForTesting public static final String EXTRA_IS_FIRST_RUN = "firstRun";
- @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
- @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
- @VisibleForTesting public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow";
+
+ /** Extra for notifying an Activity that it is inside the first SetupWizard flow or not. */
+ public static final String EXTRA_IS_FIRST_RUN = "firstRun";
+
+ /** Extra for notifying an Activity that it is inside the Deferred SetupWizard flow or not. */
+ public static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
+
+ /** Extra for notifying an Activity that it is inside the "Pre-Deferred Setup" flow. */
+ public static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
+
+ /** Extra for notifying an Activity that it is inside the "Portal Setup" flow. */
+ public static final String EXTRA_IS_PORTAL_SETUP = "portalSetup";
+
+ /**
+ * Extra for notifying an Activity that it is inside the any setup flow.
+ *
+ * <p>Apps that target API levels below {@link android.os.Build.VERSION_CODES#Q} is able to
+ * determine whether Activity is inside the any setup flow by one of {@link #EXTRA_IS_FIRST_RUN},
+ * {@link #EXTRA_IS_DEFERRED_SETUP}, and {@link #EXTRA_IS_PRE_DEFERRED_SETUP} is true.
+ */
+ public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow";
public static final String EXTRA_THEME = "theme";
public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
@@ -104,6 +121,7 @@ public final class WizardManagerHelper {
EXTRA_IS_FIRST_RUN,
EXTRA_IS_DEFERRED_SETUP,
EXTRA_IS_PRE_DEFERRED_SETUP,
+ EXTRA_IS_PORTAL_SETUP,
EXTRA_IS_SETUP_FLOW)) {
dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false));
}
diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml
index 1a5342c..07f87ed 100644
--- a/main/res/values/attrs.xml
+++ b/main/res/values/attrs.xml
@@ -32,6 +32,7 @@
This attribute will be ignored and use partner resource when inside setup wizard flow.
The default value is true. -->
<attr name="sucUsePartnerResource" format="boolean" />
+ <attr name="sucFullDynamicColor" format="boolean" />
</declare-styleable>
<!-- Status bar attributes; only takes effect on M or above -->
@@ -52,6 +53,10 @@
"android:windowTranslucentNavigation" should be set to false. -->
<attr name="sucSystemNavBarBackgroundColor" format="color" />
<attr name="sucLightSystemNavBar" format="boolean" />
+ <!-- The color for the system navigation bar divider. For this to take effect,
+ "android:windowDrawsSystemBarBackgrounds" should be set to true and
+ "android:windowTranslucentNavigation" should be set to false. -->
+ <attr name="sucSystemNavBarDividerColor" format="color" />
</declare-styleable>
<!-- FooterButton attributes -->
@@ -70,6 +75,8 @@
<enum name="skip" value="7" />
<enum name="stop" value="8" />
</attr>
+ <attr name="sucFooterButtonPaddingStart" format="dimension" />
+ <attr name="sucFooterButtonPaddingEnd" format="dimension" />
</declare-styleable>
<!-- Button of footer attributes -->
@@ -87,11 +94,18 @@
<attr name="sucFooterBarButtonColorControlHighlight" format="color" />
<attr name="sucFooterBarButtonColorControlHighlightRipple" format="color" />
<attr name="sucFooterBarPaddingVertical" format="dimension" />
+ <attr name="sucFooterBarPaddingStart" format="dimension" />
+ <attr name="sucFooterBarPaddingEnd" format="dimension" />
+ <attr name="sucFooterBarMinHeight" format="dimension" />
</declare-styleable>
<declare-styleable name="SucHeaderMixin">
<attr name="sucHeaderText" format="string" localization="suggested" />
<attr name="sucHeaderTextColor" format="reference|color" />
+ <attr name="sucGlifHeaderMarginTop" format="dimension" />
+ <attr name="sucGlifHeaderMarginBottom" format="dimension" />
+ <attr name="sucGlifIconMarginTop" format="dimension" />
+ <attr name="sucHeaderContainerMarginBottom" format="dimension" />
</declare-styleable>
</resources>
diff --git a/main/res/values/dimens.xml b/main/res/values/dimens.xml
deleted file mode 100644
index ef3d98d..0000000
--- a/main/res/values/dimens.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
-
- <!-- TODO: Remove default values from setup compat, use from theme -->
- <!-- Footer button bar style padding attributes-->
- <dimen name="suc_customization_footer_min_height">72dp</dimen>
- <dimen name="suc_customization_button_margin_start">8dp</dimen>
- <dimen name="suc_customization_button_margin_end">20dp</dimen>
-
- <!-- Footer button style padding attributes -->
- <dimen name="suc_customization_button_padding">16dp</dimen>
-
-</resources>
diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml
index 48fcddf..6474426 100644
--- a/main/res/values/styles.xml
+++ b/main/res/values/styles.xml
@@ -27,14 +27,14 @@
<item name="android:clipChildren">false</item>
<item name="android:clipToPadding">false</item>
<item name="android:gravity">center_vertical</item>
- <item name="android:minHeight">@dimen/suc_customization_footer_min_height</item>
+ <item name="android:minHeight">?attr/sucFooterBarMinHeight</item>
<item name="android:orientation">horizontal</item>
<item name="android:paddingTop">?attr/sucFooterBarPaddingVertical</item>
<item name="android:paddingBottom">?attr/sucFooterBarPaddingVertical</item>
- <item name="android:paddingEnd" tools:ignore="NewApi">@dimen/suc_customization_button_margin_end</item>
- <item name="android:paddingLeft">@dimen/suc_customization_button_margin_start</item>
- <item name="android:paddingRight">@dimen/suc_customization_button_margin_end</item>
- <item name="android:paddingStart" tools:ignore="NewApi">@dimen/suc_customization_button_margin_start</item>
+ <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterBarPaddingEnd</item>
+ <item name="android:paddingLeft">?attr/sucFooterBarPaddingStart</item>
+ <item name="android:paddingRight">?attr/sucFooterBarPaddingEnd</item>
+ <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterBarPaddingStart</item>
</style>
<style name="SucPartnerCustomizationButton.Primary" parent="android:Widget.Material.Button.Colored">
@@ -46,8 +46,10 @@
<!-- Values used in styles -->
<item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
- <item name="android:paddingLeft">@dimen/suc_customization_button_padding</item>
- <item name="android:paddingRight">@dimen/suc_customization_button_padding</item>
+ <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
+ <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
+ <item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item>
+ <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item>
<item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item>
<item name="android:stateListAnimator">@null</item>
@@ -65,8 +67,10 @@
<!-- Values used in styles -->
<item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
<item name="android:minWidth">0dp</item>
- <item name="android:paddingLeft">@dimen/suc_customization_button_padding</item>
- <item name="android:paddingRight">@dimen/suc_customization_button_padding</item>
+ <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
+ <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
+ <item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item>
+ <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item>
<item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item>
<!-- Values used in themes -->
diff --git a/partnerconfig/AndroidManifest.xml b/partnerconfig/AndroidManifest.xml
index c95a4dd..43e2041 100644
--- a/partnerconfig/AndroidManifest.xml
+++ b/partnerconfig/AndroidManifest.xml
@@ -16,9 +16,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.setupcompat.partnerconfig">
- <uses-sdk
- android:minSdkVersion="14"
- android:targetSdkVersion="28" />
+ <uses-sdk />
+
+ <!-- after SDK 30, package need to declare its visible packages. -->
+ <queries tools:node="merge">
+ <intent>
+ <action android:name="com.android.setupwizard.action.PARTNER_CUSTOMIZATION" />
+ </intent>
+ <provider android:authorities="com.google.android.setupwizard.partner" />
+ </queries>
</manifest>
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 56256c9..280ab81 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -30,9 +30,16 @@ public enum PartnerConfig {
// Navigation bar background color
CONFIG_NAVIGATION_BAR_BG_COLOR(PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR, ResourceType.COLOR),
+ // Navigation bar divider color
+ CONFIG_NAVIGATION_BAR_DIVIDER_COLOR(
+ PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR, ResourceType.COLOR),
+
// Background color of the footer bar.
CONFIG_FOOTER_BAR_BG_COLOR(PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR, ResourceType.COLOR),
+ // The min height of the footer buttons
+ CONFIG_FOOTER_BAR_MIN_HEIGHT(PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, ResourceType.DIMENSION),
+
// The same as "windowLightNavigationBar". If set true, the navigation bar icons will be drawn
// such that it is compatible with a light navigation bar background.
CONFIG_LIGHT_NAVIGATION_BAR(PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR, ResourceType.BOOL),
@@ -89,10 +96,18 @@ public enum PartnerConfig {
CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA(
PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA, ResourceType.FRACTION),
- // Text size of the primary footer button
+ // Text size of the footer buttons
CONFIG_FOOTER_BUTTON_TEXT_SIZE(
PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, ResourceType.DIMENSION),
+ // The text style of footer buttons {0 = NORMAL}, {1 = BOLD}, {2 = ITALIC}, {3 = BOLD_ITALIC}
+ CONFIG_FOOTER_BUTTON_TEXT_STYLE(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_STYLE, ResourceType.INTEGER),
+
+ // The min height of the footer buttons
+ CONFIG_FOOTER_BUTTON_MIN_HEIGHT(
+ PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, ResourceType.DIMENSION),
+
// Disabled background alpha of the footer buttons
CONFIG_FOOTER_BUTTON_DISABLED_ALPHA(
PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, ResourceType.FRACTION),
@@ -120,6 +135,12 @@ public enum PartnerConfig {
// Background color of layout
CONFIG_LAYOUT_BACKGROUND_COLOR(PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, ResourceType.COLOR),
+ // Margin start of the layout
+ CONFIG_LAYOUT_MARGIN_START(PartnerConfigKey.KEY_LAYOUT_MARGIN_START, ResourceType.DIMENSION),
+
+ // Margin end of the layout
+ CONFIG_LAYOUT_MARGIN_END(PartnerConfigKey.KEY_LAYOUT_MARGIN_END, ResourceType.DIMENSION),
+
// Text color of the header
CONFIG_HEADER_TEXT_COLOR(PartnerConfigKey.KEY_HEADER_TEXT_COLOR, ResourceType.COLOR),
@@ -129,13 +150,50 @@ public enum PartnerConfig {
// Font family of the header
CONFIG_HEADER_FONT_FAMILY(PartnerConfigKey.KEY_HEADER_FONT_FAMILY, ResourceType.STRING),
+ // Margin top of the header text
+ CONFIG_HEADER_TEXT_MARGIN_TOP(
+ PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_TOP, ResourceType.DIMENSION),
+
+ // Margin bottom of the header text
+ CONFIG_HEADER_TEXT_MARGIN_BOTTOM(
+ PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
// Gravity of the header, icon and description
CONFIG_LAYOUT_GRAVITY(PartnerConfigKey.KEY_LAYOUT_GRAVITY, ResourceType.STRING),
+ // Margin top of the icon
+ CONFIG_ICON_MARGIN_TOP(PartnerConfigKey.KEY_ICON_MARGIN_TOP, ResourceType.DIMENSION),
+
+ // Size of the icon
+ CONFIG_ICON_SIZE(PartnerConfigKey.KEY_ICON_SIZE, ResourceType.DIMENSION),
+
// Background color of the header area
CONFIG_HEADER_AREA_BACKGROUND_COLOR(
PartnerConfigKey.KEY_HEADER_AREA_BACKGROUND_COLOR, ResourceType.COLOR),
+ // Margin bottom of the header container
+ CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM(
+ PartnerConfigKey.KEY_HEADER_CONTAINER_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
+ // Auto text size enabled status
+ CONFIG_HEADER_AUTO_SIZE_ENABLED(PartnerConfigKey.KEY_HEADER_AUTO_SIZE_ENABLED, ResourceType.BOOL),
+
+ // Max text size of header when auto size enabled. Ignored if auto size is false.
+ CONFIG_HEADER_AUTO_SIZE_MAX_TEXT_SIZE(
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE, ResourceType.DIMENSION),
+
+ // Min text size of header when auto size enabled. Ignored if auto size is false.
+ CONFIG_HEADER_AUTO_SIZE_MIN_TEXT_SIZE(
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE, ResourceType.DIMENSION),
+
+ // The max lines of the max text size when auto size enabled. Ignored if auto size is false.
+ CONFIG_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE(
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE, ResourceType.INTEGER),
+
+ // Extra line spacing of header when auto size enabled. Ignored if auto size is false.
+ CONFIG_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA(
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA, ResourceType.DIMENSION),
+
// Text size of the description
CONFIG_DESCRIPTION_TEXT_SIZE(PartnerConfigKey.KEY_DESCRIPTION_TEXT_SIZE, ResourceType.DIMENSION),
@@ -149,6 +207,14 @@ public enum PartnerConfig {
// Font family of the description
CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING),
+ // Margin top of the description text
+ CONFIG_DESCRIPTION_TEXT_MARGIN_TOP(
+ PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, ResourceType.DIMENSION),
+
+ // Margin bottom of the description text
+ CONFIG_DESCRIPTION_TEXT_MARGIN_BOTTOM(
+ PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
// Text size of the body content text
CONFIG_CONTENT_TEXT_SIZE(PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, ResourceType.DIMENSION),
@@ -164,6 +230,75 @@ public enum PartnerConfig {
// Gravity of the body content text
CONFIG_CONTENT_LAYOUT_GRAVITY(PartnerConfigKey.KEY_CONTENT_LAYOUT_GRAVITY, ResourceType.STRING),
+ // The padding top of the content
+ CONFIG_CONTENT_PADDING_TOP(PartnerConfigKey.KEY_CONTENT_PADDING_TOP, ResourceType.DIMENSION),
+
+ // The text size of the content info.
+ CONFIG_CONTENT_INFO_TEXT_SIZE(
+ PartnerConfigKey.KEY_CONTENT_INFO_TEXT_SIZE, ResourceType.DIMENSION),
+
+ // The font family of the content info.
+ CONFIG_CONTENT_INFO_FONT_FAMILY(
+ PartnerConfigKey.KEY_CONTENT_INFO_FONT_FAMILY, ResourceType.STRING),
+
+ // The text line spacing extra of the content info.
+ CONFIG_CONTENT_INFO_LINE_SPACING_EXTRA(
+ PartnerConfigKey.KEY_CONTENT_INFO_LINE_SPACING_EXTRA, ResourceType.DIMENSION),
+
+ // The icon size of the content info.
+ CONFIG_CONTENT_INFO_ICON_SIZE(
+ PartnerConfigKey.KEY_CONTENT_INFO_ICON_SIZE, ResourceType.DIMENSION),
+
+ // The icon margin end of the content info.
+ CONFIG_CONTENT_INFO_ICON_MARGIN_END(
+ PartnerConfigKey.KEY_CONTENT_INFO_ICON_MARGIN_END, ResourceType.DIMENSION),
+
+ // The padding top of the content info.
+ CONFIG_CONTENT_INFO_PADDING_TOP(
+ PartnerConfigKey.KEY_CONTENT_INFO_PADDING_TOP, ResourceType.DIMENSION),
+
+ // The padding bottom of the content info.
+ CONFIG_CONTENT_INFO_PADDING_BOTTOM(
+ PartnerConfigKey.KEY_CONTENT_INFO_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+ // The title text size of list items.
+ CONFIG_ITEMS_TITLE_TEXT_SIZE(PartnerConfigKey.KEY_ITEMS_TITLE_TEXT_SIZE, ResourceType.DIMENSION),
+
+ // The summary text size of list items.
+ CONFIG_ITEMS_SUMMARY_TEXT_SIZE(
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_TEXT_SIZE, ResourceType.DIMENSION),
+
+ // The summary margin top of list items.
+ CONFIG_ITEMS_SUMMARY_MARGIN_TOP(
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_MARGIN_TOP, ResourceType.DIMENSION),
+
+ // The title font family of list items.
+ CONFIG_ITEMS_TITLE_FONT_FAMILY(PartnerConfigKey.KEY_ITEMS_TITLE_FONT_FAMILY, ResourceType.STRING),
+
+ // The summary font family of list items.
+ CONFIG_ITEMS_SUMMARY_FONT_FAMILY(
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_FONT_FAMILY, ResourceType.STRING),
+
+ // The padding top of list items.
+ CONFIG_ITEMS_PADDING_TOP(PartnerConfigKey.KEY_ITEMS_PADDING_TOP, ResourceType.DIMENSION),
+
+ // The padding bottom of list items.
+ CONFIG_ITEMS_PADDING_BOTTOM(PartnerConfigKey.KEY_ITEMS_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+ // The minimum height of list items.
+ CONFIG_ITEMS_MIN_HEIGHT(PartnerConfigKey.KEY_ITEMS_MIN_HEIGHT, ResourceType.DIMENSION),
+
+ // The divider of list items are showing on the pages.
+ CONFIG_ITEMS_DIVIDER_SHOWN(PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN, ResourceType.BOOL),
+
+ // The intrinsic width of the card view for foldabe/tablet.
+ CONFIG_CARD_VIEW_INTRINSIC_WIDTH(
+ PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH, ResourceType.DIMENSION),
+
+ // The intrinsic height of the card view for foldabe/tablet.
+ CONFIG_CARD_VIEW_INTRINSIC_HEIGHT(
+ PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT, ResourceType.DIMENSION),
+
// 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),
@@ -184,9 +319,104 @@ public enum PartnerConfig {
CONFIG_PROGRESS_ILLUSTRATION_UPDATE(
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE, ResourceType.ILLUSTRATION),
+ // The animation of loading screen used in those activities which is finishing setup.
+ // For example:com.google.android.setupwizard.FINAL_HOLD
+ CONFIG_PROGRESS_ILLUSTRATION_FINAL_HOLD(
+ PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD, ResourceType.ILLUSTRATION),
+
+ // The animation of loading screen to define how long showing on the pages.
CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS(
- PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, ResourceType.INTEGER);
+ PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, ResourceType.INTEGER),
+
+ // The animation for S+ devices used in those screens waiting for non of below type.
+ CONFIG_LOADING_LOTTIE_DEFAULT(
+ PartnerConfigKey.KEY_LOADING_LOTTIE_DEFAULT, ResourceType.ILLUSTRATION),
+
+ // The animation for S+ devices used in those screens which is processing account info or related
+ // functions.
+ // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT
+ CONFIG_LOADING_LOTTIE_ACCOUNT(
+ PartnerConfigKey.KEY_LOADING_LOTTIE_ACCOUNT, ResourceType.ILLUSTRATION),
+
+ // The animation for S+ devices used in those screens which is processing data connection.
+ // For example:com.android.setupwizard.CAPTIVE_PORTAL
+ CONFIG_LOADING_LOTTIE_CONNECTION(
+ PartnerConfigKey.KEY_LOADING_LOTTIE_CONNECTION, ResourceType.ILLUSTRATION),
+
+ // The animation for S+ devices used in those screens which is updating devices.
+ // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+ CONFIG_LOADING_LOTTIE_UPDATE(
+ PartnerConfigKey.KEY_LOADING_LOTTIE_UPDATE, ResourceType.ILLUSTRATION),
+
+ // The animation for S+ devices used in those screens which is updating devices.
+ // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+ CONFIG_LOADING_LOTTIE_FINAL_HOLD(
+ PartnerConfigKey.KEY_LOADING_LOTTIE_FINAL_HOLD, ResourceType.ILLUSTRATION),
+
+ // The transition type to decide the transition between activities or fragments.
+ CONFIG_TRANSITION_TYPE(PartnerConfigKey.KEY_TRANSITION_TYPE, ResourceType.INTEGER),
+
+ // The list of keypath and color map, applied to default animation when light theme.
+ CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_DEFAULT(
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to account animation when light theme.
+ CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_ACCOUNT(
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to connection animation when light theme.
+ CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_CONNECTION(
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to update animation when light theme.
+ CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_UPDATE(
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to update animation when light theme.
+ CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD(
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to default animation when dark theme.
+ CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_DEFAULT(
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to account animation when dark theme.
+ CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_ACCOUNT(
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to connection animation when dark theme.
+ CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_CONNECTION(
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to update animation when dark theme.
+ CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_UPDATE(
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE, ResourceType.STRING_ARRAY),
+
+ // The list of keypath and color map, applied to final hold animation when dark theme.
+ CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_FINAL_HOLD(
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD, ResourceType.STRING_ARRAY),
+
+ // The padding top of the content frame of loading layout.
+ CONFIG_LOADING_LAYOUT_PADDING_TOP(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP, ResourceType.DIMENSION),
+
+ // The padding start of the content frame of loading layout.
+ CONFIG_LOADING_LAYOUT_PADDING_START(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_START, ResourceType.DIMENSION),
+
+ // The padding end of the content frame of loading layout.
+ CONFIG_LOADING_LAYOUT_PADDING_END(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END, ResourceType.DIMENSION),
+
+ // The padding bottom of the content frame of loading layout.
+ CONFIG_LOADING_LAYOUT_PADDING_BOTTOM(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+ // The height of the header of the loading layout.
+ CONFIG_LOADING_LAYOUT_HEADER_HEIGHT(
+ PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION);
+ /** Resource type of the partner resources type. */
public enum ResourceType {
INTEGER,
BOOL,
@@ -195,7 +425,8 @@ public enum PartnerConfig {
STRING,
DIMENSION,
FRACTION,
- ILLUSTRATION;
+ ILLUSTRATION,
+ STRING_ARRAY
}
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 7b9f65b..2ca8876 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -18,22 +18,27 @@ package com.google.android.setupcompat.partnerconfig;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
+import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.EnumMap;
+import java.util.List;
/** The helper reads and caches the partner configurations from SUW. */
public class PartnerConfigHelper {
@@ -47,6 +52,22 @@ public class PartnerConfigHelper {
@VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig";
+ @VisibleForTesting
+ public static final String IS_SUW_DAY_NIGHT_ENABLED_METHOD = "isSuwDayNightEnabled";
+
+ @VisibleForTesting
+ public static final String IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD =
+ "isExtendedPartnerConfigEnabled";
+
+ @VisibleForTesting
+ public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled";
+
+ @VisibleForTesting static Bundle suwDayNightEnabledBundle = null;
+
+ @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null;
+
+ @VisibleForTesting public static Bundle applyDynamicColorBundle = null;
+
private static PartnerConfigHelper instance = null;
@VisibleForTesting Bundle resultBundle = null;
@@ -54,15 +75,44 @@ public class PartnerConfigHelper {
@VisibleForTesting
final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class);
+ private static ContentObserver contentObserver;
+
+ private static int savedConfigUiMode;
+
+ private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
+
public static synchronized PartnerConfigHelper get(@NonNull Context context) {
- if (instance == null) {
+ if (!isValidInstance(context)) {
instance = new PartnerConfigHelper(context);
}
return instance;
}
+ private static boolean isValidInstance(@NonNull Context context) {
+ Configuration currentConfig = context.getResources().getConfiguration();
+ if (instance == null) {
+ savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ savedOrientation = currentConfig.orientation;
+ return false;
+ } else {
+ if (isSetupWizardDayNightEnabled(context)
+ && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) {
+ savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ resetInstance();
+ return false;
+ } else if (currentConfig.orientation != savedOrientation) {
+ savedOrientation = currentConfig.orientation;
+ resetInstance();
+ return false;
+ }
+ }
+ return true;
+ }
+
private PartnerConfigHelper(Context context) {
getPartnerConfigBundle(context);
+
+ registerContentObserver(context);
}
/**
@@ -75,12 +125,22 @@ public class PartnerConfigHelper {
}
/**
+ * Returns whether the given {@code resourceConfig} are available. This is true if setup wizard's
+ * content provider returns us a non-empty bundle, and this result bundle includes the given
+ * {@code resourceConfig} even if all the values are default, and none are customized by the
+ * overlay APK.
+ */
+ public boolean isPartnerConfigAvailable(PartnerConfig resourceConfig) {
+ return isAvailable() && resultBundle.containsKey(resourceConfig.getResourceName());
+ }
+
+ /**
* 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
+ * @param resourceConfig The {@link PartnerConfig} of target resource
*/
@ColorInt
public int getColor(@NonNull Context context, PartnerConfig resourceConfig) {
@@ -99,6 +159,13 @@ public class PartnerConfigHelper {
Resources resource = resourceEntry.getResources();
int resId = resourceEntry.getResourceId();
+ // for @null
+ TypedValue outValue = new TypedValue();
+ resource.getValue(resId, outValue, true);
+ if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) {
+ return result;
+ }
+
if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
result = resource.getColor(resId, null);
} else {
@@ -189,6 +256,38 @@ public class PartnerConfigHelper {
}
/**
+ * Returns the string array 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
+ */
+ @NonNull
+ public List<String> getStringArray(@NonNull Context context, PartnerConfig resourceConfig) {
+ if (resourceConfig.getResourceType() != ResourceType.STRING_ARRAY) {
+ throw new IllegalArgumentException("Not a string array resource");
+ }
+
+ String[] result;
+ List<String> listResult = new ArrayList<>();
+
+ try {
+ ResourceEntry resourceEntry =
+ getResourceEntryFromKey(context, resourceConfig.getResourceName());
+ Resources resource = resourceEntry.getResources();
+ int resId = resourceEntry.getResourceId();
+
+ result = resource.getStringArray(resId);
+ Collections.addAll(listResult, result);
+ } catch (NullPointerException exception) {
+ // fall through
+ }
+
+ return listResult;
+ }
+
+ /**
* 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.
@@ -233,8 +332,8 @@ public class PartnerConfigHelper {
}
/**
- * 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
+ * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} is
+ * 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
@@ -316,6 +415,39 @@ public class PartnerConfigHelper {
}
/**
+ * Returns the integer of given {@code resourceConfig}. If the given {@code resourceConfig} is 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 int getInteger(@NonNull Context context, PartnerConfig resourceConfig, int defaultValue) {
+ if (resourceConfig.getResourceType() != ResourceType.INTEGER) {
+ throw new IllegalArgumentException("Not a integer resource");
+ }
+
+ if (partnerResourceCache.containsKey(resourceConfig)) {
+ return (int) partnerResourceCache.get(resourceConfig);
+ }
+
+ int result = defaultValue;
+ try {
+ ResourceEntry resourceEntry =
+ getResourceEntryFromKey(context, resourceConfig.getResourceName());
+ Resources resource = resourceEntry.getResources();
+ int resId = resourceEntry.getResourceId();
+
+ result = resource.getInteger(resId);
+ partnerResourceCache.put(resourceConfig, result);
+ } 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.
@@ -362,17 +494,14 @@ public class PartnerConfigHelper {
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);
+ getContentUri(),
+ SUW_GET_PARTNER_CONFIG_METHOD,
+ /* arg= */ null,
+ /* extras= */ null);
partnerResourceCache.clear();
} catch (IllegalArgumentException | SecurityException exception) {
Log.w(TAG, "Fail to get config from suw provider");
@@ -381,21 +510,134 @@ public class PartnerConfigHelper {
}
@Nullable
- private ResourceEntry getResourceEntryFromKey(Context context, String resourceName) {
+ @VisibleForTesting
+ 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(context, resourceEntryBundle);
+
+ return adjustResourceEntryDayNightMode(
+ context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+ }
+
+ /**
+ * Force to day mode if setup wizard does not support day/night mode and current system is in
+ * night mode.
+ */
+ private static ResourceEntry adjustResourceEntryDayNightMode(
+ Context context, ResourceEntry resourceEntry) {
+ Resources resource = resourceEntry.getResources();
+ Configuration configuration = resource.getConfiguration();
+ if (!isSetupWizardDayNightEnabled(context) && Util.isNightMode(configuration)) {
+ if (resourceEntry == null) {
+ Log.w(TAG, "resourceEntry is null, skip to force day mode.");
+ return resourceEntry;
+ }
+ configuration.uiMode =
+ Configuration.UI_MODE_NIGHT_NO
+ | (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+ resource.updateConfiguration(configuration, resource.getDisplayMetrics());
+ }
+
+ return resourceEntry;
}
@VisibleForTesting
- public static synchronized void resetForTesting() {
+ public static synchronized void resetInstance() {
instance = null;
+ suwDayNightEnabledBundle = null;
+ applyExtendedPartnerConfigBundle = null;
+ applyDynamicColorBundle = null;
}
- private TypedValue getTypedValueFromResource(Resources resource, int resId, int type) {
+ /**
+ * Checks whether SetupWizard supports the DayNight theme during setup flow; if return false setup
+ * flow should force to light theme.
+ *
+ * <p>Returns true if the setupwizard is listening to system DayNight theme setting.
+ */
+ public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) {
+ if (suwDayNightEnabledBundle == null) {
+ try {
+ suwDayNightEnabledBundle =
+ context
+ .getContentResolver()
+ .call(
+ getContentUri(),
+ IS_SUW_DAY_NIGHT_ENABLED_METHOD,
+ /* arg= */ null,
+ /* extras= */ null);
+ } catch (IllegalArgumentException | SecurityException exception) {
+ Log.w(TAG, "SetupWizard DayNight supporting status unknown; return as false.");
+ suwDayNightEnabledBundle = null;
+ return false;
+ }
+ }
+
+ return (suwDayNightEnabledBundle != null
+ && suwDayNightEnabledBundle.getBoolean(IS_SUW_DAY_NIGHT_ENABLED_METHOD, false));
+ }
+
+ /** Returns true if the SetupWizard supports the extended partner configs during setup flow. */
+ public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) {
+ if (applyExtendedPartnerConfigBundle == null) {
+ try {
+ applyExtendedPartnerConfigBundle =
+ context
+ .getContentResolver()
+ .call(
+ getContentUri(),
+ IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD,
+ /* arg= */ null,
+ /* extras= */ null);
+ } catch (IllegalArgumentException | SecurityException exception) {
+ Log.w(
+ TAG,
+ "SetupWizard extended partner configs supporting status unknown; return as false.");
+ applyExtendedPartnerConfigBundle = null;
+ return false;
+ }
+ }
+
+ return (applyExtendedPartnerConfigBundle != null
+ && applyExtendedPartnerConfigBundle.getBoolean(
+ IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false));
+ }
+
+ /** Returns true if the SetupWizard supports the dynamic color during setup flow. */
+ public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
+ if (applyDynamicColorBundle == null) {
+ try {
+ applyDynamicColorBundle =
+ context
+ .getContentResolver()
+ .call(
+ getContentUri(),
+ IS_DYNAMIC_COLOR_ENABLED_METHOD,
+ /* arg= */ null,
+ /* extras= */ null);
+ } catch (IllegalArgumentException | SecurityException exception) {
+ Log.w(TAG, "SetupWizard dynamic color supporting status unknown; return as false.");
+ applyDynamicColorBundle = null;
+ return false;
+ }
+ }
+
+ return (applyDynamicColorBundle != null
+ && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false));
+ }
+
+ @VisibleForTesting
+ static Uri getContentUri() {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SUW_AUTHORITY)
+ .build();
+ }
+
+ private static TypedValue getTypedValueFromResource(Resources resource, int resId, int type) {
TypedValue value = new TypedValue();
resource.getValue(resId, value, true);
if (value.type != type) {
@@ -409,8 +651,42 @@ public class PartnerConfigHelper {
return value;
}
- private float getDimensionFromTypedValue(Context context, TypedValue value) {
+ private static float getDimensionFromTypedValue(Context context, TypedValue value) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return value.getDimension(displayMetrics);
}
+
+ private static void registerContentObserver(Context context) {
+ if (isSetupWizardDayNightEnabled(context)) {
+ if (contentObserver != null) {
+ unregisterContentObserver(context);
+ }
+
+ Uri contentUri = getContentUri();
+ try {
+ contentObserver =
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ resetInstance();
+ }
+ };
+ context
+ .getContentResolver()
+ .registerContentObserver(contentUri, /* notifyForDescendants= */ true, contentObserver);
+ } catch (SecurityException | NullPointerException | IllegalArgumentException e) {
+ Log.w(TAG, "Failed to register content observer for " + contentUri + ": " + e);
+ }
+ }
+ }
+
+ private static void unregisterContentObserver(Context context) {
+ try {
+ context.getContentResolver().unregisterContentObserver(contentObserver);
+ contentObserver = null;
+ } catch (SecurityException | NullPointerException | IllegalArgumentException e) {
+ Log.w(TAG, "Failed to unregister content observer: " + e);
+ }
+ }
}
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index e5c5442..c7444a5 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -28,7 +28,9 @@ import java.lang.annotation.RetentionPolicy;
PartnerConfigKey.KEY_LIGHT_STATUS_BAR,
PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR,
PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR,
+ PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR,
PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR,
+ PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT,
PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY,
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER,
PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL,
@@ -43,6 +45,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_TEXT_STYLE,
+ PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT,
PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA,
PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR,
PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR,
@@ -50,25 +54,80 @@ import java.lang.annotation.RetentionPolicy;
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR,
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR,
+ PartnerConfigKey.KEY_LAYOUT_MARGIN_START,
+ PartnerConfigKey.KEY_LAYOUT_MARGIN_END,
PartnerConfigKey.KEY_HEADER_TEXT_SIZE,
PartnerConfigKey.KEY_HEADER_TEXT_COLOR,
PartnerConfigKey.KEY_HEADER_FONT_FAMILY,
PartnerConfigKey.KEY_HEADER_AREA_BACKGROUND_COLOR,
+ PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_TOP,
+ PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_BOTTOM,
+ PartnerConfigKey.KEY_HEADER_CONTAINER_MARGIN_BOTTOM,
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_ENABLED,
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE,
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE,
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE,
+ PartnerConfigKey.KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA,
PartnerConfigKey.KEY_LAYOUT_GRAVITY,
+ PartnerConfigKey.KEY_ICON_MARGIN_TOP,
+ PartnerConfigKey.KEY_ICON_SIZE,
PartnerConfigKey.KEY_DESCRIPTION_TEXT_SIZE,
PartnerConfigKey.KEY_DESCRIPTION_TEXT_COLOR,
PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR,
PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY,
+ PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP,
+ PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM,
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_CONTENT_PADDING_TOP,
+ PartnerConfigKey.KEY_CONTENT_INFO_TEXT_SIZE,
+ PartnerConfigKey.KEY_CONTENT_INFO_FONT_FAMILY,
+ PartnerConfigKey.KEY_CONTENT_INFO_LINE_SPACING_EXTRA,
+ PartnerConfigKey.KEY_CONTENT_INFO_ICON_SIZE,
+ PartnerConfigKey.KEY_CONTENT_INFO_ICON_MARGIN_END,
+ PartnerConfigKey.KEY_CONTENT_INFO_PADDING_TOP,
+ PartnerConfigKey.KEY_CONTENT_INFO_PADDING_BOTTOM,
+ PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH,
+ PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT,
+ PartnerConfigKey.KEY_ITEMS_TITLE_TEXT_SIZE,
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_TEXT_SIZE,
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_MARGIN_TOP,
+ PartnerConfigKey.KEY_ITEMS_TITLE_FONT_FAMILY,
+ PartnerConfigKey.KEY_ITEMS_SUMMARY_FONT_FAMILY,
+ PartnerConfigKey.KEY_ITEMS_PADDING_TOP,
+ PartnerConfigKey.KEY_ITEMS_PADDING_BOTTOM,
+ PartnerConfigKey.KEY_ITEMS_MIN_HEIGHT,
+ PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN,
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DEFAULT,
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_ACCOUNT,
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_CONNECTION,
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE,
+ PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD,
PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS,
+ PartnerConfigKey.KEY_LOADING_LOTTIE_ACCOUNT,
+ PartnerConfigKey.KEY_LOADING_LOTTIE_CONNECTION,
+ PartnerConfigKey.KEY_LOADING_LOTTIE_DEFAULT,
+ PartnerConfigKey.KEY_LOADING_LOTTIE_UPDATE,
+ PartnerConfigKey.KEY_LOADING_LOTTIE_FINAL_HOLD,
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT,
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT,
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION,
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE,
+ PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD,
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT,
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT,
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION,
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE,
+ PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD,
+ PartnerConfigKey.KEY_TRANSITION_TYPE,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_START,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM,
+ PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT,
})
// TODO: can be removed and always reference PartnerConfig.getResourceName()?
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -87,9 +146,15 @@ public @interface PartnerConfigKey {
// such that it is compatible with a light navigation bar background.
String KEY_LIGHT_NAVIGATION_BAR = "setup_compat_light_navigation_bar";
+ // Navigation bar divider color
+ String KEY_NAVIGATION_BAR_DIVIDER_COLOR = "setup_compat_navigation_bar_divider_color";
+
// Background color of the footer bar.
String KEY_FOOTER_BAR_BG_COLOR = "setup_compat_footer_bar_bg_color";
+ // The min height of the footer bar
+ String KEY_FOOTER_BAR_MIN_HEIGHT = "setup_compat_footer_bar_min_height";
+
// 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";
@@ -127,12 +192,18 @@ public @interface PartnerConfigKey {
// Corner radius of the footer buttons
String KEY_FOOTER_BUTTON_RADIUS = "setup_compat_footer_button_radius";
- // Ripple color alpha of the footer button
+ // Ripple color alpha of the footer buttons
String KEY_FOOTER_BUTTON_RIPPLE_ALPHA = "setup_compat_footer_button_ripple_alpha";
- // Text size of the footer button
+ // Text size of the footer buttons
String KEY_FOOTER_BUTTON_TEXT_SIZE = "setup_compat_footer_button_text_size";
+ // The font face used in footer buttons {0 = NORMAL}, {1 = BOLD}, {2 = ITALIC}, {3 = BOLD_ITALIC}
+ String KEY_FOOTER_BUTTON_TEXT_STYLE = "setup_compat_footer_button_text_style";
+
+ // The min height of the footer buttons
+ String KEY_FOOTER_BUTTON_MIN_HEIGHT = "setup_compat_footer_button_min_height";
+
// Disabled background alpha of the footer buttons
String KEY_FOOTER_BUTTON_DISABLED_ALPHA = "setup_compat_footer_button_disabled_alpha";
@@ -154,6 +225,12 @@ public @interface PartnerConfigKey {
// Background color of layout
String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color";
+ // Margin start of the layout
+ String KEY_LAYOUT_MARGIN_START = "setup_design_layout_margin_start";
+
+ // Margin end of the layout
+ String KEY_LAYOUT_MARGIN_END = "setup_design_layout_margin_end";
+
// Text size of the header
String KEY_HEADER_TEXT_SIZE = "setup_design_header_text_size";
@@ -163,12 +240,44 @@ public @interface PartnerConfigKey {
// Font family of the header
String KEY_HEADER_FONT_FAMILY = "setup_design_header_font_family";
+ // Margin top of the header text
+ String KEY_HEADER_TEXT_MARGIN_TOP = "setup_design_header_text_margin_top";
+
+ // Margin bottom of the header text
+ String KEY_HEADER_TEXT_MARGIN_BOTTOM = "setup_design_header_text_margin_bottom";
+
// Gravity of the header, icon and description
String KEY_LAYOUT_GRAVITY = "setup_design_layout_gravity";
+ // Margin top of the icon
+ String KEY_ICON_MARGIN_TOP = "setup_design_icon_margin_top";
+
+ // Size of the icon
+ String KEY_ICON_SIZE = "setup_design_icon_size";
+
// Background color of the header area
String KEY_HEADER_AREA_BACKGROUND_COLOR = "setup_design_header_area_background_color";
+ // Margin bottom of the header container
+ String KEY_HEADER_CONTAINER_MARGIN_BOTTOM = "setup_design_header_container_margin_bottom";
+
+ // Auto text size enabled status
+ String KEY_HEADER_AUTO_SIZE_ENABLED = "setup_design_header_auto_size_enabled";
+
+ // Max text size of header when auto size enabled. Ignored if auto size is false.
+ String KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE = "setup_design_header_auto_size_max_text_size";
+
+ // Min text size of header when auto size enabled. Ignored if auto size is false.
+ String KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE = "setup_design_header_auto_size_min_text_size";
+
+ // The max lines of the max text size when auto size enabled. Ignored if auto size is false.
+ String KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE =
+ "setup_design_header_auto_size_max_line_of_max_size";
+
+ // Extra line spacing of header when auto size enabled. Ignored if auto size is false.
+ String KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA =
+ "setup_design_header_auto_size_line_spacing_extra";
+
// Text size of the description
String KEY_DESCRIPTION_TEXT_SIZE = "setup_design_description_text_size";
@@ -181,6 +290,12 @@ public @interface PartnerConfigKey {
// Font family of the description
String KEY_DESCRIPTION_FONT_FAMILY = "setup_design_description_font_family";
+ // Margin top of the header text
+ String KEY_DESCRIPTION_TEXT_MARGIN_TOP = "setup_design_description_text_margin_top";
+
+ // Margin bottom of the header text
+ String KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM = "setup_design_description_text_margin_bottom";
+
// Text size of the body content text
String KEY_CONTENT_TEXT_SIZE = "setup_design_content_text_size";
@@ -196,6 +311,63 @@ public @interface PartnerConfigKey {
// Gravity of the body content text
String KEY_CONTENT_LAYOUT_GRAVITY = "setup_design_content_layout_gravity";
+ // The padding top of the content
+ String KEY_CONTENT_PADDING_TOP = "setup_design_content_padding_top";
+
+ // The text size of the content info.
+ String KEY_CONTENT_INFO_TEXT_SIZE = "setup_design_content_info_text_size";
+
+ // The font family of the content info.
+ String KEY_CONTENT_INFO_FONT_FAMILY = "setup_design_content_info_font_family";
+
+ // The text line spacing extra of the content info.
+ String KEY_CONTENT_INFO_LINE_SPACING_EXTRA = "setup_design_content_info_line_spacing_extra";
+
+ // The icon size of the content info.
+ String KEY_CONTENT_INFO_ICON_SIZE = "setup_design_content_info_icon_size";
+
+ // The icon margin end of the content info.
+ String KEY_CONTENT_INFO_ICON_MARGIN_END = "setup_design_content_info_icon_margin_end";
+
+ // The padding top of the content info.
+ String KEY_CONTENT_INFO_PADDING_TOP = "setup_design_content_info_padding_top";
+
+ // The padding bottom of the content info.
+ String KEY_CONTENT_INFO_PADDING_BOTTOM = "setup_design_content_info_padding_bottom";
+
+ // The title text size of list items.
+ String KEY_ITEMS_TITLE_TEXT_SIZE = "setup_design_items_title_text_size";
+
+ // The summary text size of list items.
+ String KEY_ITEMS_SUMMARY_TEXT_SIZE = "setup_design_items_summary_text_size";
+
+ // The summary margin top of list items.
+ String KEY_ITEMS_SUMMARY_MARGIN_TOP = "setup_design_items_summary_margin_top";
+
+ // The title font family of list items.
+ String KEY_ITEMS_TITLE_FONT_FAMILY = "setup_design_items_title_font_family";
+
+ // The summary font family of list items.
+ String KEY_ITEMS_SUMMARY_FONT_FAMILY = "setup_design_items_summary_font_family";
+
+ // The padding top of list items.
+ String KEY_ITEMS_PADDING_TOP = "setup_design_items_padding_top";
+
+ // The padding bottom of list items.
+ String KEY_ITEMS_PADDING_BOTTOM = "setup_design_items_padding_bottom";
+
+ // The minimum height of list items.
+ String KEY_ITEMS_MIN_HEIGHT = "setup_design_items_min_height";
+
+ // The divider of list items are showing.
+ String KEY_ITEMS_DIVIDER_SHOWN = "setup_design_items_divider_shown";
+
+ // The intrinsic width of the card view for foldabe/tablet.
+ String KEY_CARD_VIEW_INTRINSIC_WIDTH = "setup_design_card_view_intrinsic_width";
+
+ // The intrinsic height of the card view for foldabe/tablet.
+ String KEY_CARD_VIEW_INTRINSIC_HEIGHT = "setup_design_card_view_intrinsic_height";
+
// The animation of loading screen used in those activities which is non of below type.
String KEY_PROGRESS_ILLUSTRATION_DEFAULT = "progress_illustration_custom_default";
@@ -212,6 +384,94 @@ public @interface PartnerConfigKey {
// For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
String KEY_PROGRESS_ILLUSTRATION_UPDATE = "progress_illustration_custom_update";
+ // The animation of loading screen used in those activities which is updating device.
+ // For example:com.google.android.setupwizard.FINAL_HOLD
+ String KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD = "final_hold_custom_illustration";
+
// 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";
+
+ // The animation for S+ devices used in those screens waiting for non of below type.
+ String KEY_LOADING_LOTTIE_DEFAULT = "loading_animation_custom_default";
+
+ // The animation for S+ devices used in those screens which is processing account info or related
+ // functions.
+ // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT
+ String KEY_LOADING_LOTTIE_ACCOUNT = "loading_animation_custom_account";
+
+ // The animation for S+ devices used in those screens which is processing data connection.
+ // For example:com.android.setupwizard.CAPTIVE_PORTAL
+ String KEY_LOADING_LOTTIE_CONNECTION = "loading_animation_custom_connection";
+
+ // The animation for S+ devices used in those screens which is updating devices.
+ // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+ String KEY_LOADING_LOTTIE_UPDATE = "loading_animation_custom_update";
+
+ // The animation for S+ devices used in those screens which is updating devices.
+ // For example:com.google.android.setupwizard.FINAL_HOLD
+ String KEY_LOADING_LOTTIE_FINAL_HOLD = "loading_animation_custom_final_hold";
+
+ // A string-array to list all the key path and color map for default animation for light theme.
+ // For example: background:#FFFFFF
+ String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT =
+ "loading_light_theme_customization_default";
+
+ // A string-array to list all the key path and color map for account animation for light theme.
+ // For example: background:#FFFFFF
+ String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT =
+ "loading_light_theme_customization_account";
+
+ // A string-array to list all the key path and color map for connection animation for light theme.
+ // For example: background:#FFFFFF
+ String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION =
+ "loading_light_theme_customization_connection";
+
+ // A string-array to list all the key path and color map for update animation for light theme.
+ // For example: background:#FFFFFF
+ String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE = "loading_light_theme_customization_update";
+
+ // A string-array to list all the key path and color map for final hold animation for light theme.
+ // For example: background:#FFFFFF
+ String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD =
+ "loading_light_theme_customization_final_hold";
+
+ // A string-array to list all the key path and color map for default animation for dark theme.
+ // For example: background:#000000
+ String KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT = "loading_dark_theme_customization_default";
+
+ // A string-array to list all the key path and color map for account animation for dark theme.
+ // For example: background:#000000
+ String KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT = "loading_dark_theme_customization_account";
+
+ // A string-array to list all the key path and color map for connection animation for dark theme.
+ // For example: background:#000000
+ String KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION =
+ "loading_dark_theme_customization_connection";
+
+ // A string-array to list all the key path and color map for update animation for dark theme.
+ // For example: background:#000000
+ String KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE = "loading_dark_theme_customization_update";
+
+ // A string-array to list all the key path and color map for final hold animation for dark theme.
+ // For example: background:#000000
+ String KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD =
+ "loading_dark_theme_customization_final_hold";
+
+ // The transition type between activities
+ String KEY_TRANSITION_TYPE = "setup_design_transition_type";
+
+ // A padding top of the content frame of loading layout.
+ String KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP = "loading_layout_content_padding_top";
+
+ // A padding start of the content frame of loading layout.
+ String KEY_LOADING_LAYOUT_CONTENT_PADDING_START = "loading_layout_content_padding_start";
+
+ // A padding end of the content frame of loading layout.
+ String KEY_LOADING_LAYOUT_CONTENT_PADDING_END = "loading_layout_content_padding_end";
+
+ // A padding bottom of the content frame of loading layout.
+ String KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM = "loading_layout_content_padding_bottom";
+
+ // A height of the header of loading layout.
+ String KEY_LOADING_LAYOUT_HEADER_HEIGHT = "loading_layout_header_height";
}
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
index 8f7c9d8..c8b8623 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
@@ -23,10 +23,10 @@ import android.content.res.Resources;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
+import android.util.Log;
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
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java
new file mode 100644
index 0000000..8ac4c2d
--- /dev/null
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.partnerconfig;
+
+import android.content.res.Configuration;
+
+/** The utility to help partner to config. */
+public final class Util {
+
+ public static boolean isNightMode(Configuration configuration) {
+ return (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ private Util() {}
+}