Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/src/main/java/io/grpc/Grpc.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ private Grpc() {
@Documented
public @interface TransportAttr {}

/**
* Annotation for Channel attributes. It follows the annotation semantics defined
* by {@link Attributes}.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5678")
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface ChannelAttr {}

/**
* Creates a channel builder with a target string and credentials. The target can be either a
* valid {@link NameResolver}-compliant URI, or an authority string.
Expand Down
25 changes: 25 additions & 0 deletions api/src/main/java/io/grpc/NameResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.InlineMe;
import io.grpc.Grpc.ChannelAttr;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand Down Expand Up @@ -281,6 +282,7 @@ public static final class Args {
private final ProxyDetector proxyDetector;
private final SynchronizationContext syncContext;
private final ServiceConfigParser serviceConfigParser;
@Nullable private final Attributes channelAttributes;
@Nullable private final ScheduledExecutorService scheduledExecutorService;
@Nullable private final ChannelLogger channelLogger;
@Nullable private final Executor executor;
Expand All @@ -291,6 +293,7 @@ private Args(
ProxyDetector proxyDetector,
SynchronizationContext syncContext,
ServiceConfigParser serviceConfigParser,
Attributes channelAttributes,
@Nullable ScheduledExecutorService scheduledExecutorService,
@Nullable ChannelLogger channelLogger,
@Nullable Executor executor,
Expand All @@ -299,6 +302,7 @@ private Args(
this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set");
this.syncContext = checkNotNull(syncContext, "syncContext not set");
this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser not set");
this.channelAttributes = checkNotNull(channelAttributes, "channelAttributes not set");
this.scheduledExecutorService = scheduledExecutorService;
this.channelLogger = channelLogger;
this.executor = executor;
Expand Down Expand Up @@ -363,6 +367,15 @@ public ServiceConfigParser getServiceConfigParser() {
return serviceConfigParser;
}

/**
* Returns the {@link ServiceConfigParser}.
*
* @since 1.21.0
*/
public Attributes getChannelAttributes() {
return channelAttributes;
}

/**
* Returns the {@link ChannelLogger} for the Channel served by this NameResolver.
*
Expand Down Expand Up @@ -452,6 +465,7 @@ public static final class Builder {
private ProxyDetector proxyDetector;
private SynchronizationContext syncContext;
private ServiceConfigParser serviceConfigParser;
private Attributes channelAttributes = Attributes.EMPTY;
private ScheduledExecutorService scheduledExecutorService;
private ChannelLogger channelLogger;
private Executor executor;
Expand Down Expand Up @@ -510,6 +524,16 @@ public Builder setServiceConfigParser(ServiceConfigParser parser) {
return this;
}

/**
* See {@link Args#getChannelAttributes}. This is a required field, default empty.
*
* @since 1.XXX
*/
public Builder setChannelAttributes(@ChannelAttr Attributes channelAttributes) {
this.channelAttributes = checkNotNull(channelAttributes);
return this;
}

/**
* See {@link Args#getChannelLogger}.
*
Expand Down Expand Up @@ -551,6 +575,7 @@ public Args build() {
return
new Args(
defaultPort, proxyDetector, syncContext, serviceConfigParser,
channelAttributes,
scheduledExecutorService, channelLogger, executor, overrideAuthority);
}
}
Expand Down
31 changes: 30 additions & 1 deletion api/src/main/java/io/grpc/NameResolverRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public final class NameResolverRegistry {
private static final Logger logger = Logger.getLogger(NameResolverRegistry.class.getName());
private static NameResolverRegistry instance;

private final NameResolverRegistry parent;
private final NameResolver.Factory factory = new NameResolverFactory();
private static final String UNKNOWN_SCHEME = "unknown";
@GuardedBy("this")
Expand All @@ -58,6 +59,27 @@ public final class NameResolverRegistry {
@GuardedBy("this")
private ImmutableMap<String, NameResolverProvider> effectiveProviders = ImmutableMap.of();

/**
* Creates a new empty registry.
*/
public NameResolverRegistry() {
this(null);
}

/**
* Decorates another {@link NameResolverRegistry} without actually modifying it.
*
* <p>Lookups will consult both this registry and the parent registry, and will return the highest
* priority matching provider between the two of them. This is most useful when 'parent' is the
* default registry and modifying that global variable would be inappropriate.
*
* @param parent another registry to decorate
*/
@Internal
public NameResolverRegistry(NameResolverRegistry parent) {
this.parent = parent;
}

public synchronized String getDefaultScheme() {
return defaultScheme;
}
Expand All @@ -66,7 +88,14 @@ public NameResolverProvider getProviderForScheme(String scheme) {
if (scheme == null) {
return null;
}
return providers().get(scheme.toLowerCase(Locale.US));
NameResolverProvider parentResult = (parent != null)
? parent.getProviderForScheme(scheme) : null;
NameResolverProvider selfResult = providers().get(scheme.toLowerCase(Locale.US));
return (priorityOf(parentResult) > priorityOf(selfResult)) ? parentResult : selfResult;
}

private static int priorityOf(NameResolverProvider provider) {
return provider != null ? provider.priority() : Integer.MIN_VALUE;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.fail;

import android.content.Context;
import android.content.Intent;
Expand Down Expand Up @@ -59,6 +60,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
Expand Down Expand Up @@ -239,6 +241,21 @@ public void testConnectViaTargetUri() throws Exception {
assertThat(doCall("Hello").get()).isEqualTo("Hello");
}

@Test
public void testConnectViaIntentTargetUri() throws Exception {
// TODO(jdcormie): Make this test good.
channel = BinderChannelBuilder.forTarget("intent://foo/bar", appContext).build();
// channel = BinderChannelBuilder.forTarget("android-app://com.foo.bar/authoritaaay", appContext).build();
ListenableFuture<String> resultFuture = doCall("Hello");
try {
resultFuture.get();
fail();
} catch (ExecutionException ee) {
StatusRuntimeException sre = (StatusRuntimeException) ee.getCause();
assertThat(sre.getStatus().getCode()).isEqualTo(Code.UNIMPLEMENTED);
}
}

@Test
public void testConnectViaIntentFilter() throws Exception {
// Compare with the <intent-filter> mapping in AndroidManifest.xml.
Expand All @@ -263,7 +280,7 @@ public void testUncaughtServerException() throws Exception {
CallOptions callOptions = CallOptions.DEFAULT.withDeadlineAfter(5, SECONDS);
try {
ClientCalls.blockingUnaryCall(interceptedChannel, method, callOptions, "hello");
Assert.fail();
fail();
} catch (StatusRuntimeException e) {
// We don't care how *our* RPC failed, but make sure we didn't have to rely on the deadline.
assertThat(e.getStatus().getCode()).isNotEqualTo(Code.DEADLINE_EXCEEDED);
Expand All @@ -278,7 +295,7 @@ public void testUncaughtClientException() throws Exception {
CallOptions callOptions = CallOptions.DEFAULT.withDeadlineAfter(5, SECONDS);
try {
ClientCalls.blockingUnaryCall(channel, method, callOptions, "hello");
Assert.fail();
fail();
} catch (StatusRuntimeException e) {
// We don't care *how* our RPC failed, but make sure we didn't have to rely on the deadline.
assertThat(e.getStatus().getCode()).isNotEqualTo(Code.DEADLINE_EXCEEDED);
Expand Down
7 changes: 7 additions & 0 deletions binder/src/main/java/io/grpc/binder/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package io.grpc.binder;

import android.content.Intent;
import android.os.UserHandle;
import io.grpc.Attributes;
import io.grpc.ExperimentalApi;
import io.grpc.Grpc.ChannelAttr;

/** Constant parts of the gRPC binder transport public API. */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
Expand All @@ -29,4 +32,8 @@ private ApiConstants() {}
* themselves in a {@link android.app.Service#onBind(Intent)} call.
*/
public static final String ACTION_BIND = "grpc.io.action.BIND";

@ChannelAttr
public static final Attributes.Key<UserHandle> CHANNEL_ATTR_TARGET_USER =
Attributes.Key.create("io.grpc.binder.CHANNEL_ATTR_TARGET_USER");
}
11 changes: 11 additions & 0 deletions binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import android.os.UserHandle;
import androidx.annotation.RequiresApi;
import com.google.errorprone.annotations.DoNotCall;
import io.grpc.Attributes;
import io.grpc.ExperimentalApi;
import io.grpc.ForwardingChannelBuilder;
import io.grpc.ManagedChannel;
Expand Down Expand Up @@ -159,6 +160,7 @@ public static BinderChannelBuilder forTarget(String target) {

private final ManagedChannelImplBuilder managedChannelImplBuilder;
private final BinderClientTransportFactory.Builder transportFactoryBuilder;
private final Attributes.Builder channelAttrBuilder = Attributes.newBuilder();

private boolean strictLifecycleManagement;

Expand All @@ -179,6 +181,13 @@ private BinderChannelBuilder(
} else {
managedChannelImplBuilder =
new ManagedChannelImplBuilder(target, transportFactoryBuilder, null);
if (target.startsWith("intent:")) {
// We register our resolver here instead of using SPI:
// 1) So the 99% of Android apps that don't use grpc-binder and don't use indirect
// addressing don't have to pay to load our classes.
// 2) It's our first opportunity to access the Application Context.
IntentNameResolverProvider.createAndRegisterSingletonOnce(sourceContext.getApplicationContext());
}
}
idleTimeout(60, TimeUnit.SECONDS);
}
Expand Down Expand Up @@ -250,6 +259,7 @@ public BinderChannelBuilder securityPolicy(SecurityPolicy securityPolicy) {
@RequiresApi(30)
public BinderChannelBuilder bindAsUser(UserHandle targetUserHandle) {
transportFactoryBuilder.setTargetUserHandle(targetUserHandle);
channelAttrBuilder.set(ApiConstants.CHANNEL_ATTR_TARGET_USER, targetUserHandle);
return this;
}

Expand Down Expand Up @@ -284,6 +294,7 @@ public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
public ManagedChannel build() {
transportFactoryBuilder.setOffloadExecutorPool(
managedChannelImplBuilder.getOffloadExecutorPool());
managedChannelImplBuilder.setChannelAttributes(channelAttrBuilder.build());
return super.build();
}
}
Loading