aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwwtbuaa01 <133191176+wwtbuaa01@users.noreply.github.com>2023-06-29 19:01:13 +0800
committerGitHub <noreply@github.com>2023-06-29 13:01:13 +0200
commit5799febde0160634ba39abfb10fe772386046082 (patch)
treefb360a25fe1d5c7d70df4877806c4181acd8272f
parent4d2c3aac0ef7d35ac363ef328f1005366758cf94 (diff)
downloadgrpc-grpc-java-5799febde0160634ba39abfb10fe772386046082.tar.gz
Add UserHandle and BinderChannelCredentials to BinderChannelBuilder to support cross-user communication through OnDeviceServer. (#10197)
Add UserHandle and BinderChannelCredentials to BinderChannelBuilder to support cross-user ondevice server.
-rw-r--r--binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java3
-rw-r--r--binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java3
-rw-r--r--binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java98
-rw-r--r--binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java70
-rw-r--r--binder/src/main/java/io/grpc/binder/internal/BinderTransport.java6
-rw-r--r--binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java87
-rw-r--r--binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java32
-rw-r--r--binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java88
8 files changed, 375 insertions, 12 deletions
diff --git a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
index dbacf3517..44454fbf7 100644
--- a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
+++ b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
@@ -35,6 +35,7 @@ import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.BinderServerBuilder;
import io.grpc.binder.HostServices;
import io.grpc.binder.InboundParcelablePolicy;
@@ -146,7 +147,9 @@ public final class BinderClientTransportTest {
public BinderTransport.BinderClientTransport build() {
return new BinderTransport.BinderClientTransport(
appContext,
+ BinderChannelCredentials.forDefault(),
serverAddress,
+ null,
BindServiceFlags.DEFAULTS,
ContextCompat.getMainExecutor(appContext),
executorServicePool,
diff --git a/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java b/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
index 5a1b302f7..5140057d7 100644
--- a/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
+++ b/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
@@ -24,6 +24,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import io.grpc.ServerStreamTracer;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.HostServices;
import io.grpc.binder.InboundParcelablePolicy;
import io.grpc.binder.SecurityPolicies;
@@ -95,7 +96,9 @@ public final class BinderTransportTest extends AbstractTransportTest {
AndroidComponentAddress addr = (AndroidComponentAddress) server.getListenSocketAddress();
return new BinderTransport.BinderClientTransport(
appContext,
+ BinderChannelCredentials.forDefault(),
addr,
+ null,
BindServiceFlags.DEFAULTS,
ContextCompat.getMainExecutor(appContext),
executorServicePool,
diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
index 214eb6dc4..e71476131 100644
--- a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
+++ b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Context;
+import android.os.UserHandle;
import androidx.core.content.ContextCompat;
import com.google.errorprone.annotations.DoNotCall;
import io.grpc.ChannelCredentials;
@@ -71,7 +72,37 @@ public final class BinderChannelBuilder
public static BinderChannelBuilder forAddress(
AndroidComponentAddress directAddress, Context sourceContext) {
return new BinderChannelBuilder(
- checkNotNull(directAddress, "directAddress"), null, sourceContext);
+ checkNotNull(directAddress, "directAddress"),
+ null,
+ sourceContext,
+ BinderChannelCredentials.forDefault());
+ }
+
+ /**
+ * Creates a channel builder that will bind to a remote Android service with provided
+ * BinderChannelCredentials.
+ *
+ * <p>The underlying Android binding will be torn down when the channel becomes idle. This happens
+ * after 30 minutes without use by default but can be configured via {@link
+ * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link
+ * ManagedChannel#enterIdle()}.
+ *
+ * <p>You the caller are responsible for managing the lifecycle of any channels built by the
+ * resulting builder. They will not be shut down automatically.
+ *
+ * @param directAddress the {@link AndroidComponentAddress} referencing the service to bind to.
+ * @param sourceContext the context to bind from (e.g. The current Activity or Application).
+ * @param channelCredentials the arbitrary binder specific channel credentials to be used to
+ * establish a binder connection.
+ * @return a new builder
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public static BinderChannelBuilder forAddress(
+ AndroidComponentAddress directAddress,
+ Context sourceContext,
+ BinderChannelCredentials channelCredentials) {
+ return new BinderChannelBuilder(
+ checkNotNull(directAddress, "directAddress"), null, sourceContext, channelCredentials);
}
/**
@@ -92,7 +123,37 @@ public final class BinderChannelBuilder
* @return a new builder
*/
public static BinderChannelBuilder forTarget(String target, Context sourceContext) {
- return new BinderChannelBuilder(null, checkNotNull(target, "target"), sourceContext);
+ return new BinderChannelBuilder(
+ null,
+ checkNotNull(target, "target"),
+ sourceContext,
+ BinderChannelCredentials.forDefault());
+ }
+
+ /**
+ * Creates a channel builder that will bind to a remote Android service, via a string target name
+ * which will be resolved.
+ *
+ * <p>The underlying Android binding will be torn down when the channel becomes idle. This happens
+ * after 30 minutes without use by default but can be configured via {@link
+ * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link
+ * ManagedChannel#enterIdle()}.
+ *
+ * <p>You the caller are responsible for managing the lifecycle of any channels built by the
+ * resulting builder. They will not be shut down automatically.
+ *
+ * @param target A target uri which should resolve into an {@link AndroidComponentAddress}
+ * referencing the service to bind to.
+ * @param sourceContext the context to bind from (e.g. The current Activity or Application).
+ * @param channelCredentials the arbitrary binder specific channel credentials to be used to
+ * establish a binder connection.
+ * @return a new builder
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public static BinderChannelBuilder forTarget(
+ String target, Context sourceContext, BinderChannelCredentials channelCredentials) {
+ return new BinderChannelBuilder(
+ null, checkNotNull(target, "target"), sourceContext, channelCredentials);
}
/**
@@ -121,12 +182,14 @@ public final class BinderChannelBuilder
private SecurityPolicy securityPolicy;
private InboundParcelablePolicy inboundParcelablePolicy;
private BindServiceFlags bindServiceFlags;
+ @Nullable private UserHandle targetUserHandle;
private boolean strictLifecycleManagement;
private BinderChannelBuilder(
@Nullable AndroidComponentAddress directAddress,
@Nullable String target,
- Context sourceContext) {
+ Context sourceContext,
+ BinderChannelCredentials channelCredentials) {
mainThreadExecutor =
ContextCompat.getMainExecutor(checkNotNull(sourceContext, "sourceContext"));
securityPolicy = SecurityPolicies.internalOnly();
@@ -139,10 +202,12 @@ public final class BinderChannelBuilder
public ClientTransportFactory buildClientTransportFactory() {
return new TransportFactory(
sourceContext,
+ channelCredentials,
mainThreadExecutor,
schedulerPool,
managedChannelImplBuilder.getOffloadExecutorPool(),
securityPolicy,
+ targetUserHandle,
bindServiceFlags,
inboundParcelablePolicy);
}
@@ -216,6 +281,23 @@ public final class BinderChannelBuilder
return this;
}
+/**
+ * Provides the target {@UserHandle} of the remote Android service.
+ *
+ * <p>When targetUserHandle is set, Context.bindServiceAsUser will used and additional Android
+ * permissions will be required. If your usage does not require cross-user communications, please
+ * do not set this field. It is the caller's responsibility to make sure that it holds the
+ * corresponding permissions.
+ *
+ * @param targetUserHandle the target user to bind into.
+ * @return this
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public BinderChannelBuilder bindAsUser(UserHandle targetUserHandle) {
+ this.targetUserHandle = targetUserHandle;
+ return this;
+ }
+
/** Sets the policy for inbound parcelable objects. */
public BinderChannelBuilder inboundParcelablePolicy(
InboundParcelablePolicy inboundParcelablePolicy) {
@@ -245,12 +327,14 @@ public final class BinderChannelBuilder
/** Creates new binder transports. */
private static final class TransportFactory implements ClientTransportFactory {
private final Context sourceContext;
+ private final BinderChannelCredentials channelCredentials;
private final Executor mainThreadExecutor;
private final ObjectPool<ScheduledExecutorService> scheduledExecutorPool;
private final ObjectPool<? extends Executor> offloadExecutorPool;
private final SecurityPolicy securityPolicy;
- private final InboundParcelablePolicy inboundParcelablePolicy;
+ @Nullable private final UserHandle targetUserHandle;
private final BindServiceFlags bindServiceFlags;
+ private final InboundParcelablePolicy inboundParcelablePolicy;
private ScheduledExecutorService executorService;
private Executor offloadExecutor;
@@ -258,17 +342,21 @@ public final class BinderChannelBuilder
TransportFactory(
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
Executor mainThreadExecutor,
ObjectPool<ScheduledExecutorService> scheduledExecutorPool,
ObjectPool<? extends Executor> offloadExecutorPool,
SecurityPolicy securityPolicy,
+ @Nullable UserHandle targetUserHandle,
BindServiceFlags bindServiceFlags,
InboundParcelablePolicy inboundParcelablePolicy) {
this.sourceContext = sourceContext;
+ this.channelCredentials = channelCredentials;
this.mainThreadExecutor = mainThreadExecutor;
this.scheduledExecutorPool = scheduledExecutorPool;
this.offloadExecutorPool = offloadExecutorPool;
this.securityPolicy = securityPolicy;
+ this.targetUserHandle = targetUserHandle;
this.bindServiceFlags = bindServiceFlags;
this.inboundParcelablePolicy = inboundParcelablePolicy;
@@ -284,7 +372,9 @@ public final class BinderChannelBuilder
}
return new BinderTransport.BinderClientTransport(
sourceContext,
+ channelCredentials,
(AndroidComponentAddress) addr,
+ targetUserHandle,
bindServiceFlags,
mainThreadExecutor,
scheduledExecutorPool,
diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java b/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java
new file mode 100644
index 000000000..1fa2136e4
--- /dev/null
+++ b/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 The gRPC Authors
+ *
+ * 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 io.grpc.binder;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.ComponentName;
+import io.grpc.ChannelCredentials;
+import io.grpc.ExperimentalApi;
+import javax.annotation.Nullable;
+
+/** Additional arbitrary arguments to establish a Android binder connection channel. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+public final class BinderChannelCredentials extends ChannelCredentials {
+
+ /**
+ * Creates the default BinderChannelCredentials.
+ *
+ * @return a BinderChannelCredentials
+ */
+ public static BinderChannelCredentials forDefault() {
+ return new BinderChannelCredentials(null);
+ }
+
+ /**
+ * Creates a BinderChannelCredentials to be used with DevicePolicyManager API.
+ *
+ * @param devicePolicyAdminComponentName the admin component to be specified with
+ * DevicePolicyManager.bindDeviceAdminServiceAsUser API.
+ * @return a BinderChannelCredentials
+ */
+ public static BinderChannelCredentials forDevicePolicyAdmin(
+ ComponentName devicePolicyAdminComponentName) {
+ return new BinderChannelCredentials(devicePolicyAdminComponentName);
+ }
+
+ @Nullable private final ComponentName devicePolicyAdminComponentName;
+
+ private BinderChannelCredentials(@Nullable ComponentName devicePolicyAdminComponentName) {
+ this.devicePolicyAdminComponentName = devicePolicyAdminComponentName;
+ }
+
+ @Override
+ public ChannelCredentials withoutBearerTokens() {
+ return this;
+ }
+
+ /**
+ * Returns the admin component to be specified with DevicePolicyManager
+ * bindDeviceAdminServiceAsUser API.
+ */
+ @Nullable
+ public ComponentName getDevicePolicyAdminComponentName() {
+ return devicePolicyAdminComponentName;
+ }
+}
diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
index 70b891651..cb2fe5be3 100644
--- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
+++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
@@ -28,6 +28,7 @@ import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
+import android.os.UserHandle;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.util.concurrent.ListenableFuture;
@@ -46,6 +47,7 @@ import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.InboundParcelablePolicy;
import io.grpc.binder.SecurityPolicy;
import io.grpc.internal.ClientStream;
@@ -568,7 +570,9 @@ public abstract class BinderTransport
public BinderClientTransport(
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
AndroidComponentAddress targetAddress,
+ @Nullable UserHandle targetUserHandle,
BindServiceFlags bindServiceFlags,
Executor mainThreadExecutor,
ObjectPool<ScheduledExecutorService> executorServicePool,
@@ -590,7 +594,9 @@ public abstract class BinderTransport
new ServiceBinding(
mainThreadExecutor,
sourceContext,
+ channelCredentials,
targetAddress.asBindIntent(),
+ targetUserHandle,
bindServiceFlags.toInteger(),
this);
}
diff --git a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
index 650ead9bc..32d0e7a4a 100644
--- a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
+++ b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
@@ -16,15 +16,20 @@
package io.grpc.binder.internal;
+import static com.google.common.base.Preconditions.checkState;
+
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
+import android.os.UserHandle;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status;
+import io.grpc.binder.BinderChannelCredentials;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -56,7 +61,26 @@ final class ServiceBinding implements Bindable, ServiceConnection {
UNBOUND,
}
+ // Type of the method used when binding the service.
+ private enum BindMethodType {
+ BIND_SERVICE("bindService"),
+ BIND_SERVICE_AS_USER("bindServiceAsUser"),
+ DEVICE_POLICY_BIND_SEVICE_ADMIN("DevicePolicyManager.bindDeviceAdminServiceAsUser");
+
+ private final String methodName;
+
+ BindMethodType(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public String methodName() {
+ return methodName;
+ }
+ }
+
+ private final BinderChannelCredentials channelCredentials;
private final Intent bindIntent;
+ @Nullable private final UserHandle targetUserHandle;
private final int bindFlags;
private final Observer observer;
private final Executor mainThreadExecutor;
@@ -76,7 +100,9 @@ final class ServiceBinding implements Bindable, ServiceConnection {
ServiceBinding(
Executor mainThreadExecutor,
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
Intent bindIntent,
+ @Nullable UserHandle targetUserHandle,
int bindFlags,
Observer observer) {
// We need to synchronize here ensure other threads see all
@@ -87,6 +113,8 @@ final class ServiceBinding implements Bindable, ServiceConnection {
this.observer = observer;
this.sourceContext = sourceContext;
this.mainThreadExecutor = mainThreadExecutor;
+ this.channelCredentials = channelCredentials;
+ this.targetUserHandle = targetUserHandle;
state = State.NOT_BINDING;
reportedState = State.NOT_BINDING;
}
@@ -117,7 +145,14 @@ final class ServiceBinding implements Bindable, ServiceConnection {
public synchronized void bind() {
if (state == State.NOT_BINDING) {
state = State.BINDING;
- Status bindResult = bindInternal(sourceContext, bindIntent, this, bindFlags);
+ Status bindResult =
+ bindInternal(
+ sourceContext,
+ bindIntent,
+ this,
+ bindFlags,
+ channelCredentials,
+ targetUserHandle);
if (!bindResult.isOk()) {
handleBindServiceFailure(sourceContext, this);
state = State.UNBOUND;
@@ -127,19 +162,57 @@ final class ServiceBinding implements Bindable, ServiceConnection {
}
private static Status bindInternal(
- Context context, Intent bindIntent, ServiceConnection conn, int flags) {
+ Context context,
+ Intent bindIntent,
+ ServiceConnection conn,
+ int flags,
+ BinderChannelCredentials channelCredentials,
+ @Nullable UserHandle targetUserHandle) {
+ BindMethodType bindMethodType = BindMethodType.BIND_SERVICE;
try {
- if (!context.bindService(bindIntent, conn, flags)) {
+ if (targetUserHandle == null) {
+ checkState(
+ channelCredentials.getDevicePolicyAdminComponentName() == null,
+ "BindingChannelCredentials is expected to have null devicePolicyAdmin when"
+ + " targetUserHandle is not set");
+ } else {
+ if (channelCredentials.getDevicePolicyAdminComponentName() != null) {
+ bindMethodType = BindMethodType.DEVICE_POLICY_BIND_SEVICE_ADMIN;
+ } else {
+ bindMethodType = BindMethodType.BIND_SERVICE_AS_USER;
+ }
+ }
+ boolean bindResult = false;
+ switch (bindMethodType) {
+ case BIND_SERVICE:
+ bindResult = context.bindService(bindIntent, conn, flags);
+ break;
+ case BIND_SERVICE_AS_USER:
+ bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
+ break;
+ case DEVICE_POLICY_BIND_SEVICE_ADMIN:
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ bindResult = devicePolicyManager.bindDeviceAdminServiceAsUser(
+ channelCredentials.getDevicePolicyAdminComponentName(),
+ bindIntent,
+ conn,
+ flags,
+ targetUserHandle);
+ break;
+ }
+ if (!bindResult) {
return Status.UNIMPLEMENTED.withDescription(
- "bindService(" + bindIntent + ") returned false");
+ bindMethodType.methodName() + "(" + bindIntent + ") returned false");
}
return Status.OK;
} catch (SecurityException e) {
- return Status.PERMISSION_DENIED.withCause(e).withDescription(
- "SecurityException from bindService");
+ return Status.PERMISSION_DENIED
+ .withCause(e)
+ .withDescription("SecurityException from " + bindMethodType.methodName());
} catch (RuntimeException e) {
return Status.INTERNAL.withCause(e).withDescription(
- "RuntimeException from bindService");
+ "RuntimeException from " + bindMethodType.methodName());
}
}
diff --git a/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java b/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java
new file mode 100644
index 000000000..d31065dfe
--- /dev/null
+++ b/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java
@@ -0,0 +1,32 @@
+package io.grpc.binder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BinderChannelCredentialsTest {
+ private final Context appContext = ApplicationProvider.getApplicationContext();
+
+ @Test
+ public void defaultBinderChannelCredentials() {
+ BinderChannelCredentials channelCredentials = BinderChannelCredentials.forDefault();
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName()).isNull();
+ }
+
+ @Test
+ public void binderChannelCredentialsForDevicePolicyAdmin() {
+ String deviceAdminClassName = "DevicePolicyAdmin";
+ BinderChannelCredentials channelCredentials =
+ BinderChannelCredentials.forDevicePolicyAdmin(
+ new ComponentName(appContext, deviceAdminClassName));
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName()).isNotNull();
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName().getClassName())
+ .isEqualTo(deviceAdminClassName);
+ }
+}
diff --git a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
index 967e91b82..3ec65624f 100644
--- a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
+++ b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
@@ -24,15 +24,21 @@ import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
import android.app.Application;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.UserHandle;
import androidx.core.content.ContextCompat;
import androidx.test.core.app.ApplicationProvider;
import io.grpc.Status;
import io.grpc.Status.Code;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.internal.Bindable.Observer;
+import java.util.Arrays;
+import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +50,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowDevicePolicyManager;
@LooperMode(PAUSED)
@RunWith(RobolectricTestRunner.class)
@@ -256,6 +263,48 @@ public final class ServiceBindingTest {
shadowOf(getMainLooper()).idle();
}
+ @Test
+ @Config(sdk = 30)
+ public void testBindWithTargetUserHandle() throws Exception {
+ binding =
+ newBuilder().setTargetUserHandle(generateUserHandle(/* userId= */ 0)).build();
+ shadowOf(getMainLooper()).idle();
+
+ binding.bind();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(shadowApplication.getBoundServiceConnections()).isNotEmpty();
+ assertThat(observer.gotBoundEvent).isTrue();
+ assertThat(observer.binder).isSameInstanceAs(mockBinder);
+ assertThat(observer.gotUnboundEvent).isFalse();
+ assertThat(binding.isSourceContextCleared()).isFalse();
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testBindWithDeviceAdmin() throws Exception {
+ String deviceAdminClassName = "DevicePolicyAdmin";
+ ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
+ allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
+ binding =
+ newBuilder()
+ .setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
+ .setTargetUserHandle(generateUserHandle(/* userId= */ 0))
+ .setChannelCredentials(
+ BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
+ .build();
+ shadowOf(getMainLooper()).idle();
+
+ binding.bind();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(shadowApplication.getBoundServiceConnections()).isNotEmpty();
+ assertThat(observer.gotBoundEvent).isTrue();
+ assertThat(observer.binder).isSameInstanceAs(mockBinder);
+ assertThat(observer.gotUnboundEvent).isFalse();
+ assertThat(binding.isSourceContextCleared()).isFalse();
+ }
+
private void assertNoLockHeld() {
try {
binding.wait(1);
@@ -268,6 +317,28 @@ public final class ServiceBindingTest {
}
}
+ private static void allowBindDeviceAdminForUser(Context context, ComponentName admin, int userId) {
+ ShadowDevicePolicyManager devicePolicyManager =
+ shadowOf(context.getSystemService(DevicePolicyManager.class));
+ devicePolicyManager.setDeviceOwner(admin);
+ devicePolicyManager.setBindDeviceAdminTargetUsers(
+ Arrays.asList(UserHandle.getUserHandleForUid(userId)));
+ shadowOf((DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ devicePolicyManager.setDeviceOwner(admin);
+ devicePolicyManager.setBindDeviceAdminTargetUsers(
+ Arrays.asList(generateUserHandle(userId)));
+ }
+
+ /** Generate UserHandles the hard way. */
+ private static UserHandle generateUserHandle(int userId) {
+ Parcel userParcel = Parcel.obtain();
+ userParcel.writeInt(userId);
+ userParcel.setDataPosition(0);
+ UserHandle userHandle = new UserHandle(userParcel);
+ userParcel.recycle();
+ return userHandle;
+ }
+
private class TestObserver implements Bindable.Observer {
public boolean gotBoundEvent;
@@ -298,9 +369,11 @@ public final class ServiceBindingTest {
private Observer observer;
private Intent bindIntent = new Intent();
private int bindServiceFlags;
+ @Nullable private UserHandle targetUserHandle = null;
+ private BinderChannelCredentials channelCredentials = BinderChannelCredentials.forDefault();
public ServiceBindingBuilder setSourceContext(Context sourceContext) {
- this.sourceContext = sourceContext;
+ this.sourceContext = sourceContext;
return this;
}
@@ -324,11 +397,24 @@ public final class ServiceBindingTest {
return this;
}
+ public ServiceBindingBuilder setTargetUserHandle(UserHandle targetUserHandle) {
+ this.targetUserHandle = targetUserHandle;
+ return this;
+ }
+
+ public ServiceBindingBuilder setChannelCredentials(
+ BinderChannelCredentials channelCredentials) {
+ this.channelCredentials = channelCredentials;
+ return this;
+ }
+
public ServiceBinding build() {
return new ServiceBinding(
ContextCompat.getMainExecutor(sourceContext),
sourceContext,
+ channelCredentials,
bindIntent,
+ targetUserHandle,
bindServiceFlags,
observer);
}