summaryrefslogtreecommitdiff
path: root/main/java/com/google/android/setupcompat/portal/PortalHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'main/java/com/google/android/setupcompat/portal/PortalHelper.java')
-rw-r--r--main/java/com/google/android/setupcompat/portal/PortalHelper.java303
1 files changed, 303 insertions, 0 deletions
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() {}
+}
+
+