mirror of https://github.com/grpc/grpc-java.git
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.
This commit is contained in:
parent
4d2c3aac0e
commit
5799febde0
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue