diff options
author | wwtbuaa01 <133191176+wwtbuaa01@users.noreply.github.com> | 2023-06-29 19:01:13 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-29 13:01:13 +0200 |
commit | 5799febde0160634ba39abfb10fe772386046082 (patch) | |
tree | fb360a25fe1d5c7d70df4877806c4181acd8272f | |
parent | 4d2c3aac0ef7d35ac363ef328f1005366758cf94 (diff) | |
download | grpc-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.
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); } |