diff options
author | Setup Wizard Team <android-setup-team-eng@google.com> | 2021-05-11 03:40:50 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-05-11 03:40:50 +0000 |
commit | ab07602b78e7131e0dd54b86cd892f580b63210c (patch) | |
tree | 207692f7699ca2f9ba84ea76a3199e6484696f55 | |
parent | 61703d05595b45eb82bfa2f8e7fd15775343b2ad (diff) | |
parent | d77ab242a37188333aa7f74eb3d3486a8e0f200d (diff) | |
download | setupcompat-ab07602b78e7131e0dd54b86cd892f580b63210c.tar.gz |
Import updated Android SetupCompat Library 372106188 am: d77ab242a3
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/setupcompat/+/14320431
Change-Id: I0c95fe7e8a5f894f6c27ea24d1a3136c37898dd4
15 files changed, 1108 insertions, 10 deletions
@@ -19,6 +19,52 @@ license { ], } +filegroup { + name: "Aidls", + srcs: [ + "main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl", + ], + path: "main/aidl", +} + +filegroup { + name: "AidlsPortal", + srcs: [ + "main/aidl/com/google/android/setupcompat/portal/*.aidl", + ], + path: "main/aidl", +} + +filegroup { + name: "Srcs", + srcs: [ + "main/java/com/google/android/setupcompat/*.java", + "main/java/com/google/android/setupcompat/internal/*.java", + "main/java/com/google/android/setupcompat/logging/*.java", + "main/java/com/google/android/setupcompat/logging/internal/*.java", + "main/java/com/google/android/setupcompat/template/*.java", + "main/java/com/google/android/setupcompat/util/*.java", + "main/java/com/google/android/setupcompat/view/*.java", + ], + path: "main/java", +} + +filegroup { + name: "SrcsPartnerConfig", + srcs: [ + "partnerconfig/java/**/*.java", + ], + path: "partnerconfig/java", +} + +filegroup { + name: "SrcsPortal", + srcs: [ + "main/java/com/google/android/setupcompat/portal/*.java", + ], + path: "main/java", +} + android_library { name: "setupcompat", manifest: "AndroidManifest.xml", @@ -26,9 +72,11 @@ android_library { "main/res", ], srcs: [ - "main/java/**/*.java", - "partnerconfig/java/**/*.java", - "main/aidl/**/*.aidl", + ":Aidls", + ":AidlsPortal", + ":Srcs", + ":SrcsPartnerConfig", + ":SrcsPortal", ], static_libs: [ "androidx.annotation_annotation", diff --git a/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 @@ public class FooterBarMixin implements Mixin { @ColorInt private final int footerBarPrimaryBackgroundColor; @ColorInt private final int footerBarSecondaryBackgroundColor; private boolean removeFooterBarWhenEmpty = true; + private boolean isSecondaryButtonInPrimaryStyle = false; private static final float DEFAULT_DISABLED_ALPHA = 0.26f; private static final AtomicInteger nextGeneratedId = new AtomicInteger(1); @@ -115,7 +116,7 @@ public class FooterBarMixin implements Mixin { 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 @@ public class FooterBarMixin implements Mixin { /** Sets secondary button for footer. */ @MainThread public void setSecondaryButton(FooterButton footerButton) { + setSecondaryButton(footerButton, /*usePrimaryStyle= */ false); + } + + /** Sets secondary button for footer. Allow to use the primary button style. */ + @MainThread + public void setSecondaryButton(FooterButton footerButton, boolean usePrimaryStyle) { ensureOnMainThread("setSecondaryButton"); + isSecondaryButtonInPrimaryStyle = usePrimaryStyle; ensureFooterInflated(); // Setup button partner config @@ -393,16 +401,25 @@ public class FooterBarMixin implements Mixin { .setPartnerTheme( getPartnerTheme( footerButton, - /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Secondary, - /* buttonBackgroundColorConfig= */ PartnerConfig - .CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) - .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) + /* defaultPartnerTheme= */ usePrimaryStyle + ? R.style.SucPartnerCustomizationButton_Primary + : R.style.SucPartnerCustomizationButton_Secondary, + /* buttonBackgroundColorConfig= */ usePrimaryStyle + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) + .setButtonBackgroundConfig( + usePrimaryStyle + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) - .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setTextColorConfig( + usePrimaryStyle + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -435,6 +452,16 @@ public class FooterBarMixin implements Mixin { buttonContainer.removeAllViews(); if (tempSecondaryButton != null) { + if (isSecondaryButtonInPrimaryStyle) { + // Since the secondary button has the same style (with background) as the primary button, + // we need to have the left padding equal to the right padding. + updateFooterBarPadding( + buttonContainer, + buttonContainer.getPaddingRight(), + buttonContainer.getPaddingTop(), + buttonContainer.getPaddingRight(), + buttonContainer.getPaddingBottom()); + } buttonContainer.addView(tempSecondaryButton); } addSpace(); 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 @@ public enum PartnerConfig { // 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 @@ import java.lang.annotation.RetentionPolicy; 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 @@ public @interface PartnerConfigKey { // 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"; } |