Import updated Android SetupCompat Library 372106188 am: d77ab242a3
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/setupcompat/+/14320431
Change-Id: Ife454d9e1c12956dd10d43470f6a87e191abbf17
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/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 7575905..9fd8cef 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -96,6 +96,7 @@
@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);
@@ -115,7 +116,7 @@
if (applyPartnerResources) {
updateButtonTextColorWithPartnerConfig(
button,
- (id == primaryButtonId)
+ (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
: PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR);
}
@@ -384,7 +385,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
@@ -393,16 +401,25 @@
.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)
@@ -435,6 +452,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();
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 439dea2..c10055e 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -402,7 +402,11 @@
// The padding bottom of the content frame of loading layout.
CONFIG_LOADING_LAYOUT_PADDING_BOTTOM(
- PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM, ResourceType.DIMENSION);
+ 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/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index 8a775ec..a810908 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -125,6 +125,7 @@
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)
@@ -462,4 +463,7 @@
// 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";
}