Import updated Android SetupCompat Library 369420590 am: 56ea8be381 am: 3e44677b94
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/setupcompat/+/14260711
Change-Id: I924ee1edb6b169e0ec13c4fd2621fc0caebf2464
diff --git a/Android.bp b/Android.bp
index 353706d..01e654d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,52 @@
],
}
+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",
@@ -26,9 +72,11 @@
"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/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 0f0a58e..466f036 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -280,9 +280,6 @@
* {@code false}.
*/
public boolean shouldApplyDynamicColor() {
- if (!enablePartnerResourceLoading()) {
- return false;
- }
if (!useDynamicColor) {
return false;
}
@@ -295,7 +292,11 @@
return true;
}
- /** Returns boolean value of the {@code app:sucFullDynamicColor}. */
+ /**
+ * 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/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..370db96
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalHelper.java
@@ -0,0 +1,303 @@
+/*
+ * 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 android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.android.setupcompat.internal.Preconditions;
+import com.google.android.setupcompat.portal.PortalConstants.RemainingValues;
+
+/** This class is responsible for safely executing methods on SetupNotificationService. */
+public class PortalHelper {
+
+ private static final String TAG = "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(TAG, "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(TAG, "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.w(TAG, "Failed to invoke SetupNotificationService#isPortalAvailable");
+ listener.onResult(false);
+ }
+ }
+ context.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ if (!bindSetupNotificationService(context, connection)) {
+ Log.e(
+ TAG,
+ "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(TAG, "Failed to invoke SetupNotificationService#isProgressServiceAlive");
+ listener.onResult(false);
+ }
+ }
+ context.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ if (!bindSetupNotificationService(context, connection)) {
+ Log.e(
+ TAG,
+ "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/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index 8584527..8f807e6 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -24,19 +24,10 @@
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.util.StateSet;
-import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,7 +48,6 @@
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;
@@ -79,7 +69,7 @@
@VisibleForTesting final boolean applyPartnerResources;
@VisibleForTesting final boolean applyDynamicColor;
- final boolean useFullDynamicColor;
+ @VisibleForTesting final boolean useFullDynamicColor;
private LinearLayout buttonContainer;
private FooterButton primaryButton;
@@ -97,8 +87,8 @@
@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();
@@ -116,7 +106,7 @@
if (applyPartnerResources) {
updateButtonTextColorWithPartnerConfig(
button,
- (id == primaryButtonId)
+ (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
: PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR);
}
@@ -283,13 +273,14 @@
return;
}
- @ColorInt
- int color =
- PartnerConfigHelper.get(context)
- .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR);
- buttonContainer.setBackgroundColor(color);
-
- // TODO: Apply dynamic color on footer bar background if useFullDynamicColor is true
+ // 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)
@@ -345,13 +336,7 @@
/* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary,
/* buttonBackgroundColorConfig= */ PartnerConfig
.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR))
- .setButtonBackgroundConfig(
- (applyDynamicColor
- && PartnerConfigHelper.get(context)
- .isPartnerConfigAvailable(
- PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR))
- ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_DYNAMIC_COLOR
- : PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
+ .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
.setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
.setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
.setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType()))
@@ -397,7 +382,14 @@
/** 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
@@ -406,21 +398,24 @@
.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(
- (applyDynamicColor
- && PartnerConfigHelper.get(context)
- .isPartnerConfigAvailable(
- PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR))
- ? PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR
+ 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)
@@ -454,6 +449,16 @@
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();
@@ -470,7 +475,7 @@
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
}
@@ -601,224 +606,53 @@
private void onFooterButtonApplyPartnerResource(
Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) {
if (!applyPartnerResources) {
- // dynamic color should still work if not applying partner resource
- if (applyDynamicColor) {
- // override config related to dynamic color
- updateButtonTextColorWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextColorConfig());
- updateButtonBackgroundWithPartnerConfig(
- button,
- footerButtonPartnerConfig.getButtonBackgroundConfig(),
- footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
- footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
- updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig);
-
- // TODO: Apply full dynamic color if useFullDynamicColor is true
- }
return;
}
- updateButtonTextColorWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextColorConfig());
- updateButtonTextSizeWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonTextSizeConfig());
- updateButtonMinHeightWithPartnerConfig(
- button, footerButtonPartnerConfig.getButtonMinHeightConfig());
- updateButtonTypeFaceWithPartnerConfig(
+
+ // If dynamic color enabled, these colors won't be overrode by partner config.
+ // Instead, these colors align with the current theme colors.
+ if (!applyDynamicColor) {
+ updateButtonTextColorWithPartnerConfig(
+ button, footerButtonPartnerConfig.getButtonTextColorConfig());
+ FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
+ context,
+ button,
+ footerButtonPartnerConfig.getButtonBackgroundConfig(),
+ footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
+ footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
+ FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig(
+ context, button, footerButtonPartnerConfig);
+ }
+
+ FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonTextSizeConfig());
+ FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonMinHeightConfig());
+ FooterButtonStyleUtils.updateButtonTypeFaceWithPartnerConfig(
+ context,
button,
footerButtonPartnerConfig.getButtonTextTypeFaceConfig(),
footerButtonPartnerConfig.getButtonTextStyleConfig());
- updateButtonBackgroundWithPartnerConfig(
+ FooterButtonStyleUtils.updateButtonRadiusWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonRadiusConfig());
+ FooterButtonStyleUtils.updateButtonIconWithPartnerConfig(
+ context,
button,
- footerButtonPartnerConfig.getButtonBackgroundConfig(),
- footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
- footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
- updateButtonRadiusWithPartnerConfig(button, footerButtonPartnerConfig.getButtonRadiusConfig());
- updateButtonIconWithPartnerConfig(button, footerButtonPartnerConfig.getButtonIconConfig());
- updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig);
+ footerButtonPartnerConfig.getButtonIconConfig(),
+ primaryButtonId == button.getId());
}
private void updateButtonTextColorWithPartnerConfig(
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 updateButtonMinHeightWithPartnerConfig(
- 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);
- }
- }
- }
-
- private void updateButtonTypeFaceWithPartnerConfig(
- 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);
- }
- }
-
- @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);
}
}
@@ -857,43 +691,6 @@
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/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
new file mode 100644
index 0000000..98a1426
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -0,0 +1,276 @@
+/*
+ * 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.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;
+
+ static void updateButtonTextEnabledColorWithPartnerConfig(
+ Context context, Button button, PartnerConfig buttonEnableTextColorConfig) {
+ @ColorInt
+ int color = PartnerConfigHelper.get(context).getColor(context, buttonEnableTextColorConfig);
+ if (color != Color.TRANSPARENT) {
+ button.setTextColor(ColorStateList.valueOf(color));
+ }
+ }
+
+ static void updateButtonTextDisableColor(Button button, ColorStateList defaultTextColor) {
+ // TODO : add disable footer button text color partner config
+
+ // disable state will use the default disable state color
+ button.setTextColor(defaultTextColor);
+ }
+
+ @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 disabledColor;
+ float disabledAlpha;
+ int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
+ int[] ENABLED_STATE_SET = {};
+ @ColorInt
+ int 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
+ 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);
+ }
+ }
+
+ static void updateButtonRippleColorWithPartnerConfig(
+ Context context, 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};
+ // Get partner text color.
+ @ColorInt
+ int 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);
+ }
+ }
+
+ 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 isPrimary) {
+ if (button == null) {
+ return;
+ }
+ Drawable icon = null;
+ if (buttonIconConfig != null) {
+ icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig);
+ }
+ setButtonIcon(button, icon, isPrimary);
+ }
+
+ private static void setButtonIcon(Button button, Drawable icon, boolean isPrimary) {
+ 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 (isPrimary) {
+ 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) {
+ 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 a818793..c0f1c45 100644
--- a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
@@ -112,10 +112,14 @@
*/
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 8050406..32c708c 100644
--- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
@@ -47,6 +47,7 @@
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 @@
this.applyPartnerResources =
layout instanceof PartnerCustomizationLayout
&& ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
+
+ this.useFullDynamicColor =
+ layout instanceof PartnerCustomizationLayout
+ && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
}
/**
@@ -109,10 +114,14 @@
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);
}
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index cacbf80..c10055e 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -120,10 +120,6 @@
CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR(
PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR),
- // Dynamic background color of the primary footer button
- CONFIG_FOOTER_PRIMARY_BUTTON_BG_DYNAMIC_COLOR(
- PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_DYNAMIC_COLOR, ResourceType.COLOR),
-
// Text color of the primary footer button
CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR(
PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, ResourceType.COLOR),
@@ -136,10 +132,6 @@
CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR(
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, ResourceType.COLOR),
- // Dynamic text color of the secondary footer button
- CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR(
- PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR, ResourceType.COLOR),
-
// Background color of layout
CONFIG_LAYOUT_BACKGROUND_COLOR(PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, ResourceType.COLOR),
@@ -394,7 +386,27 @@
// 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);
+ 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 {
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index dd43989..2ca8876 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -607,7 +607,7 @@
}
/** Returns true if the SetupWizard supports the dynamic color during setup flow. */
- public static boolean shouldApplyDynamicColor(@NonNull Context context) {
+ public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
if (applyDynamicColorBundle == null) {
try {
applyDynamicColorBundle =
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index aa77ee8..a810908 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -50,11 +50,9 @@
PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA,
PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR,
PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR,
- PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_DYNAMIC_COLOR,
PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR,
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR,
PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
- PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR,
PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR,
PartnerConfigKey.KEY_LAYOUT_MARGIN_START,
PartnerConfigKey.KEY_LAYOUT_MARGIN_END,
@@ -123,6 +121,11 @@
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)
@@ -208,10 +211,6 @@
// Background color of the primary footer button
String KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR = "setup_compat_footer_primary_button_bg_color";
- // Dynamic background color of the primary footer button
- String KEY_FOOTER_PRIMARY_BUTTON_BG_DYNAMIC_COLOR =
- "setup_compat_footer_1st_button_bg_dynamic_color";
-
// Text color of the primary footer button
String KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR = "setup_compat_footer_primary_button_text_color";
@@ -221,10 +220,6 @@
// Text color of the secondary footer button
String KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR = "setup_compat_footer_secondary_button_text_color";
- // Dynamic text color of the secondary footer button
- String KEY_FOOTER_SECONDARY_BUTTON_TEXT_DYNAMIC_COLOR =
- "setup_compat_footer_2nd_button_text_dynamic_color";
-
// Background color of layout
String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color";
@@ -456,4 +451,19 @@
// 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";
}