diff --git a/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java index f4c5df485a..e40691e453 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; +import android.util.Log; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; @@ -31,6 +32,8 @@ import io.grpc.internal.ConnectionClientTransport; import io.grpc.internal.GrpcUtil; import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.TransportTracer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.Executor; @@ -46,6 +49,8 @@ import org.chromium.net.ExperimentalCronetEngine; public final class CronetChannelBuilder extends AbstractManagedChannelImplBuilder { + private static final String LOG_TAG = "CronetChannelBuilder"; + /** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */ public static abstract class StreamBuilderFactory { public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder( @@ -245,6 +250,11 @@ public final class CronetChannelBuilder extends * StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced. */ private static class TaggingStreamFactory extends StreamBuilderFactory { + private static volatile boolean loadSetTrafficStatsTagAttempted; + private static volatile boolean loadSetTrafficStatsUidAttempted; + private static volatile Method setTrafficStatsTagMethod; + private static volatile Method setTrafficStatsUidMethod; + private final CronetEngine cronetEngine; private final boolean trafficStatsTagSet; private final int trafficStatsTag; @@ -271,12 +281,70 @@ public final class CronetChannelBuilder extends ((ExperimentalCronetEngine) cronetEngine) .newBidirectionalStreamBuilder(url, callback, executor); if (trafficStatsTagSet) { - builder.setTrafficStatsTag(trafficStatsTag); + setTrafficStatsTag(builder, trafficStatsTag); } if (trafficStatsUidSet) { - builder.setTrafficStatsUid(trafficStatsUid); + setTrafficStatsUid(builder, trafficStatsUid); } return builder; } + + private static void setTrafficStatsTag(ExperimentalBidirectionalStream.Builder builder, + int tag) { + if (!loadSetTrafficStatsTagAttempted) { + synchronized (TaggingStreamFactory.class) { + if (!loadSetTrafficStatsTagAttempted) { + try { + setTrafficStatsTagMethod = ExperimentalBidirectionalStream.Builder.class + .getMethod("setTrafficStatsTag", int.class); + } catch (NoSuchMethodException e) { + Log.w(LOG_TAG, + "Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsTag", + e); + } finally { + loadSetTrafficStatsTagAttempted = true; + } + } + } + } + if (setTrafficStatsTagMethod != null) { + try { + setTrafficStatsTagMethod.invoke(builder, tag); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause()); + } catch (IllegalAccessException e) { + Log.w(LOG_TAG, "Failed to set traffic stats tag: " + tag, e); + } + } + } + + private static void setTrafficStatsUid(ExperimentalBidirectionalStream.Builder builder, + int uid) { + if (!loadSetTrafficStatsUidAttempted) { + synchronized (TaggingStreamFactory.class) { + if (!loadSetTrafficStatsUidAttempted) { + try { + setTrafficStatsUidMethod = ExperimentalBidirectionalStream.Builder.class + .getMethod("setTrafficStatsUid", int.class); + } catch (NoSuchMethodException e) { + Log.w(LOG_TAG, + "Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsUid", + e); + } finally { + loadSetTrafficStatsUidAttempted = true; + } + } + } + } + if (setTrafficStatsUidMethod != null) { + try { + setTrafficStatsUidMethod.invoke(builder, uid); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause()); + } catch (IllegalAccessException e) { + Log.w(LOG_TAG, "Failed to set traffic stats uid: " + uid, e); + } + } + } } } diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java index 72bd32810a..4fde69d30f 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java @@ -40,6 +40,8 @@ import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportFrameUtil; import io.grpc.internal.TransportTracer; import io.grpc.internal.WritableBuffer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -65,6 +67,9 @@ class CronetClientStream extends AbstractClientStream { private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0); private static final String LOG_TAG = "grpc-java-cronet"; + private static volatile boolean loadAddRequestAnnotationAttempted; + private static volatile Method addRequestAnnotationMethod; + @Deprecated static final CallOptions.Key CRONET_ANNOTATION_KEY = CallOptions.Key.create("cronet-annotation"); @@ -182,12 +187,16 @@ class CronetClientStream extends AbstractClientStream { if (delayRequestHeader) { builder.delayRequestHeadersUntilFirstFlush(true); } - if (annotation != null) { - ((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(annotation); - } - if (annotations != null) { - for (Object o : annotations) { - ((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(o); + if (annotation != null || annotations != null) { + ExperimentalBidirectionalStream.Builder expBidiStreamBuilder = + (ExperimentalBidirectionalStream.Builder) builder; + if (annotation != null) { + addRequestAnnotation(expBidiStreamBuilder, annotation); + } + if (annotations != null) { + for (Object o : annotations) { + addRequestAnnotation(expBidiStreamBuilder, o); + } } } setGrpcHeaders(builder); @@ -359,6 +368,35 @@ class CronetClientStream extends AbstractClientStream { && !TE_HEADER.name().equalsIgnoreCase(key); } + private static void addRequestAnnotation(ExperimentalBidirectionalStream.Builder builder, + Object annotation) { + if (!loadAddRequestAnnotationAttempted) { + synchronized (CronetClientStream.class) { + if (!loadAddRequestAnnotationAttempted) { + try { + addRequestAnnotationMethod = ExperimentalBidirectionalStream.Builder.class + .getMethod("addRequestAnnotation", Object.class); + } catch (NoSuchMethodException e) { + Log.w(LOG_TAG, + "Failed to load method ExperimentalBidirectionalStream.Builder.addRequestAnnotation", + e); + } finally { + loadAddRequestAnnotationAttempted = true; + } + } + } + } + if (addRequestAnnotationMethod != null) { + try { + addRequestAnnotationMethod.invoke(builder, annotation); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause()); + } catch (IllegalAccessException e) { + Log.w(LOG_TAG, "Failed to add request annotation: " + annotation, e); + } + } + } + private void setGrpcHeaders(BidirectionalStream.Builder builder) { // Psuedo-headers are set by cronet. // All non-pseudo headers must come after pseudo headers.