core: Inline AbstractManagedChannelImplBuilder

diff --git a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java
deleted file mode 100644
index aac6c25..0000000
--- a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright 2014 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.internal;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.MoreExecutors;
-import io.grpc.Attributes;
-import io.grpc.BinaryLog;
-import io.grpc.ClientInterceptor;
-import io.grpc.CompressorRegistry;
-import io.grpc.DecompressorRegistry;
-import io.grpc.EquivalentAddressGroup;
-import io.grpc.InternalChannelz;
-import io.grpc.ManagedChannel;
-import io.grpc.ManagedChannelBuilder;
-import io.grpc.NameResolver;
-import io.grpc.NameResolverRegistry;
-import io.grpc.ProxyDetector;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.Nullable;
-
-/**
- * Abstract base class for channel builders.
- *
- * @param <T> The concrete type of this builder.
- */
-public abstract class AbstractManagedChannelImplBuilder
-        <T extends AbstractManagedChannelImplBuilder<T>> extends ManagedChannelBuilder<T> {
-  private static final String DIRECT_ADDRESS_SCHEME = "directaddress";
-
-  private static final Logger log =
-      Logger.getLogger(AbstractManagedChannelImplBuilder.class.getName());
-
-  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
-    throw new UnsupportedOperationException("Subclass failed to hide static factory");
-  }
-
-  public static ManagedChannelBuilder<?> forTarget(String target) {
-    throw new UnsupportedOperationException("Subclass failed to hide static factory");
-  }
-
-  /**
-   * An idle timeout larger than this would disable idle mode.
-   */
-  @VisibleForTesting
-  static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
-
-  /**
-   * The default idle timeout.
-   */
-  @VisibleForTesting
-  static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
-
-  /**
-   * An idle timeout smaller than this would be capped to it.
-   */
-  static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
-
-  private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
-      SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
-
-  private static final DecompressorRegistry DEFAULT_DECOMPRESSOR_REGISTRY =
-      DecompressorRegistry.getDefaultInstance();
-
-  private static final CompressorRegistry DEFAULT_COMPRESSOR_REGISTRY =
-      CompressorRegistry.getDefaultInstance();
-
-  private static final long DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24;  // 16M
-  private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M
-
-  ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
-
-  ObjectPool<? extends Executor> offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
-
-  private final List<ClientInterceptor> interceptors = new ArrayList<>();
-  final NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
-
-  // Access via getter, which may perform authority override as needed
-  private NameResolver.Factory nameResolverFactory = nameResolverRegistry.asFactory();
-
-  final String target;
-
-  @Nullable
-  private final SocketAddress directServerAddress;
-
-  @Nullable
-  String userAgent;
-
-  @VisibleForTesting
-  @Nullable
-  String authorityOverride;
-
-  String defaultLbPolicy = GrpcUtil.DEFAULT_LB_POLICY;
-
-  boolean fullStreamDecompression;
-
-  DecompressorRegistry decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
-
-  CompressorRegistry compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
-
-  long idleTimeoutMillis = IDLE_MODE_DEFAULT_TIMEOUT_MILLIS;
-
-  int maxRetryAttempts = 5;
-  int maxHedgedAttempts = 5;
-  long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES;
-  long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES;
-  boolean retryEnabled = false; // TODO(zdapeng): default to true
-  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
-  // what should be the desired behavior for retry + stats/tracing.
-  // TODO(zdapeng): delete me
-  boolean temporarilyDisableRetry;
-
-  InternalChannelz channelz = InternalChannelz.instance();
-  int maxTraceEvents;
-
-  @Nullable
-  Map<String, ?> defaultServiceConfig;
-  boolean lookUpServiceConfig = true;
-
-  protected TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
-
-  private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
-
-  @Nullable
-  BinaryLog binlog;
-
-  @Nullable
-  ProxyDetector proxyDetector;
-
-  /**
-   * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages
-   * larger than this limit is received it will not be processed and the RPC will fail with
-   * RESOURCE_EXHAUSTED.
-   */
-  // Can be overridden by subclasses.
-  @Override
-  public T maxInboundMessageSize(int max) {
-    checkArgument(max >= 0, "negative max");
-    maxInboundMessageSize = max;
-    return thisT();
-  }
-
-  protected final int maxInboundMessageSize() {
-    return maxInboundMessageSize;
-  }
-
-  private boolean statsEnabled = true;
-  private boolean recordStartedRpcs = true;
-  private boolean recordFinishedRpcs = true;
-  private boolean recordRealTimeMetrics = false;
-  private boolean tracingEnabled = true;
-
-  protected AbstractManagedChannelImplBuilder(String target) {
-    this.target = Preconditions.checkNotNull(target, "target");
-    this.directServerAddress = null;
-  }
-
-  /**
-   * Returns a target string for the SocketAddress. It is only used as a placeholder, because
-   * DirectAddressNameResolverFactory will not actually try to use it. However, it must be a valid
-   * URI.
-   */
-  @VisibleForTesting
-  static String makeTargetStringForDirectAddress(SocketAddress address) {
-    try {
-      return new URI(DIRECT_ADDRESS_SCHEME, "", "/" + address, null).toString();
-    } catch (URISyntaxException e) {
-      // It should not happen.
-      throw new RuntimeException(e);
-    }
-  }
-
-  protected AbstractManagedChannelImplBuilder(SocketAddress directServerAddress, String authority) {
-    this.target = makeTargetStringForDirectAddress(directServerAddress);
-    this.directServerAddress = directServerAddress;
-    this.nameResolverFactory = new DirectAddressNameResolverFactory(directServerAddress, authority);
-  }
-
-  @Override
-  public final T directExecutor() {
-    return executor(MoreExecutors.directExecutor());
-  }
-
-  @Override
-  public final T executor(Executor executor) {
-    if (executor != null) {
-      this.executorPool = new FixedObjectPool<>(executor);
-    } else {
-      this.executorPool = DEFAULT_EXECUTOR_POOL;
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T offloadExecutor(Executor executor) {
-    if (executor != null) {
-      this.offloadExecutorPool = new FixedObjectPool<>(executor);
-    } else {
-      this.offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T intercept(List<ClientInterceptor> interceptors) {
-    this.interceptors.addAll(interceptors);
-    return thisT();
-  }
-
-  @Override
-  public final T intercept(ClientInterceptor... interceptors) {
-    return intercept(Arrays.asList(interceptors));
-  }
-
-  @Deprecated
-  @Override
-  public final T nameResolverFactory(NameResolver.Factory resolverFactory) {
-    Preconditions.checkState(directServerAddress == null,
-        "directServerAddress is set (%s), which forbids the use of NameResolverFactory",
-        directServerAddress);
-    if (resolverFactory != null) {
-      this.nameResolverFactory = resolverFactory;
-    } else {
-      this.nameResolverFactory = nameResolverRegistry.asFactory();
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T defaultLoadBalancingPolicy(String policy) {
-    Preconditions.checkState(directServerAddress == null,
-        "directServerAddress is set (%s), which forbids the use of load-balancing policy",
-        directServerAddress);
-    Preconditions.checkArgument(policy != null, "policy cannot be null");
-    this.defaultLbPolicy = policy;
-    return thisT();
-  }
-
-  @Override
-  public final T enableFullStreamDecompression() {
-    this.fullStreamDecompression = true;
-    return thisT();
-  }
-
-  @Override
-  public final T decompressorRegistry(DecompressorRegistry registry) {
-    if (registry != null) {
-      this.decompressorRegistry = registry;
-    } else {
-      this.decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T compressorRegistry(CompressorRegistry registry) {
-    if (registry != null) {
-      this.compressorRegistry = registry;
-    } else {
-      this.compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T userAgent(@Nullable String userAgent) {
-    this.userAgent = userAgent;
-    return thisT();
-  }
-
-  @Override
-  public final T overrideAuthority(String authority) {
-    this.authorityOverride = checkAuthority(authority);
-    return thisT();
-  }
-
-  @Override
-  public final T idleTimeout(long value, TimeUnit unit) {
-    checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
-    // We convert to the largest unit to avoid overflow
-    if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
-      // This disables idle mode
-      this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
-    } else {
-      this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
-    }
-    return thisT();
-  }
-
-  @Override
-  public final T maxRetryAttempts(int maxRetryAttempts) {
-    this.maxRetryAttempts = maxRetryAttempts;
-    return thisT();
-  }
-
-  @Override
-  public final T maxHedgedAttempts(int maxHedgedAttempts) {
-    this.maxHedgedAttempts = maxHedgedAttempts;
-    return thisT();
-  }
-
-  @Override
-  public final T retryBufferSize(long bytes) {
-    checkArgument(bytes > 0L, "retry buffer size must be positive");
-    retryBufferSize = bytes;
-    return thisT();
-  }
-
-  @Override
-  public final T perRpcBufferLimit(long bytes) {
-    checkArgument(bytes > 0L, "per RPC buffer limit must be positive");
-    perRpcBufferLimit = bytes;
-    return thisT();
-  }
-
-  @Override
-  public final T disableRetry() {
-    retryEnabled = false;
-    return thisT();
-  }
-
-  @Override
-  public final T enableRetry() {
-    retryEnabled = true;
-    statsEnabled = false;
-    tracingEnabled = false;
-    return thisT();
-  }
-
-  @Override
-  public final T setBinaryLog(BinaryLog binlog) {
-    this.binlog = binlog;
-    return thisT();
-  }
-
-  @Override
-  public T maxTraceEvents(int maxTraceEvents) {
-    checkArgument(maxTraceEvents >= 0, "maxTraceEvents must be non-negative");
-    this.maxTraceEvents = maxTraceEvents;
-    return thisT();
-  }
-
-  @Override
-  public T proxyDetector(@Nullable ProxyDetector proxyDetector) {
-    this.proxyDetector = proxyDetector;
-    return thisT();
-  }
-
-  @Override
-  public T defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
-    // TODO(notcarl): use real parsing
-    defaultServiceConfig = checkMapEntryTypes(serviceConfig);
-    return thisT();
-  }
-
-  @Nullable
-  private static Map<String, ?> checkMapEntryTypes(@Nullable Map<?, ?> map) {
-    if (map == null) {
-      return null;
-    }
-    // Not using ImmutableMap.Builder because of extra guava dependency for Android.
-    Map<String, Object> parsedMap = new LinkedHashMap<>();
-    for (Map.Entry<?, ?> entry : map.entrySet()) {
-      checkArgument(
-          entry.getKey() instanceof String,
-          "The key of the entry '%s' is not of String type", entry);
-
-      String key = (String) entry.getKey();
-      Object value = entry.getValue();
-      if (value == null) {
-        parsedMap.put(key, null);
-      } else if (value instanceof Map) {
-        parsedMap.put(key, checkMapEntryTypes((Map<?, ?>) value));
-      } else if (value instanceof List) {
-        parsedMap.put(key, checkListEntryTypes((List<?>) value));
-      } else if (value instanceof String) {
-        parsedMap.put(key, value);
-      } else if (value instanceof Double) {
-        parsedMap.put(key, value);
-      } else if (value instanceof Boolean) {
-        parsedMap.put(key, value);
-      } else {
-        throw new IllegalArgumentException(
-            "The value of the map entry '" + entry + "' is of type '" + value.getClass()
-                + "', which is not supported");
-      }
-    }
-    return Collections.unmodifiableMap(parsedMap);
-  }
-
-  private static List<?> checkListEntryTypes(List<?> list) {
-    List<Object> parsedList = new ArrayList<>(list.size());
-    for (Object value : list) {
-      if (value == null) {
-        parsedList.add(null);
-      } else if (value instanceof Map) {
-        parsedList.add(checkMapEntryTypes((Map<?, ?>) value));
-      } else if (value instanceof List) {
-        parsedList.add(checkListEntryTypes((List<?>) value));
-      } else if (value instanceof String) {
-        parsedList.add(value);
-      } else if (value instanceof Double) {
-        parsedList.add(value);
-      } else if (value instanceof Boolean) {
-        parsedList.add(value);
-      } else {
-        throw new IllegalArgumentException(
-            "The entry '" + value + "' is of type '" + value.getClass()
-                + "', which is not supported");
-      }
-    }
-    return Collections.unmodifiableList(parsedList);
-  }
-
-  @Override
-  public T disableServiceConfigLookUp() {
-    this.lookUpServiceConfig = false;
-    return thisT();
-  }
-
-  /**
-   * Disable or enable stats features. Enabled by default.
-   *
-   * <p>For the current release, calling {@code setStatsEnabled(true)} may have a side effect that
-   * disables retry.
-   */
-  protected void setStatsEnabled(boolean value) {
-    statsEnabled = value;
-  }
-
-  /**
-   * Disable or enable stats recording for RPC upstarts.  Effective only if {@link
-   * #setStatsEnabled} is set to true.  Enabled by default.
-   */
-  protected void setStatsRecordStartedRpcs(boolean value) {
-    recordStartedRpcs = value;
-  }
-
-  /**
-   * Disable or enable stats recording for RPC completions.  Effective only if {@link
-   * #setStatsEnabled} is set to true.  Enabled by default.
-   */
-  protected void setStatsRecordFinishedRpcs(boolean value) {
-    recordFinishedRpcs = value;
-  }
-
-  /**
-   * Disable or enable real-time metrics recording.  Effective only if {@link #setStatsEnabled} is
-   * set to true.  Disabled by default.
-   */
-  protected void setStatsRecordRealTimeMetrics(boolean value) {
-    recordRealTimeMetrics = value;
-  }
-
-  /**
-   * Disable or enable tracing features.  Enabled by default.
-   *
-   * <p>For the current release, calling {@code setTracingEnabled(true)} may have a side effect that
-   * disables retry.
-   */
-  protected void setTracingEnabled(boolean value) {
-    tracingEnabled = value;
-  }
-
-  @VisibleForTesting
-  final long getIdleTimeoutMillis() {
-    return idleTimeoutMillis;
-  }
-
-  /**
-   * Verifies the authority is valid.  This method exists as an escape hatch for putting in an
-   * authority that is valid, but would fail the default validation provided by this
-   * implementation.
-   */
-  protected String checkAuthority(String authority) {
-    return GrpcUtil.checkAuthority(authority);
-  }
-
-  @Override
-  public ManagedChannel build() {
-    return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
-        this,
-        buildTransportFactory(),
-        new ExponentialBackoffPolicy.Provider(),
-        SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR),
-        GrpcUtil.STOPWATCH_SUPPLIER,
-        getEffectiveInterceptors(),
-        TimeProvider.SYSTEM_TIME_PROVIDER));
-  }
-
-  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
-  // what should be the desired behavior for retry + stats/tracing.
-  // TODO(zdapeng): FIX IT
-  @VisibleForTesting
-  final List<ClientInterceptor> getEffectiveInterceptors() {
-    List<ClientInterceptor> effectiveInterceptors =
-        new ArrayList<>(this.interceptors);
-    temporarilyDisableRetry = false;
-    if (statsEnabled) {
-      temporarilyDisableRetry = true;
-      ClientInterceptor statsInterceptor = null;
-      try {
-        Class<?> censusStatsAccessor =
-            Class.forName("io.grpc.census.InternalCensusStatsAccessor");
-        Method getClientInterceptorMethod =
-            censusStatsAccessor.getDeclaredMethod(
-                "getClientInterceptor",
-                boolean.class,
-                boolean.class,
-                boolean.class);
-        statsInterceptor =
-            (ClientInterceptor) getClientInterceptorMethod
-                .invoke(
-                    null,
-                    recordStartedRpcs,
-                    recordFinishedRpcs,
-                    recordRealTimeMetrics);
-      } catch (ClassNotFoundException e) {
-        // Replace these separate catch statements with multicatch when Android min-API >= 19
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (NoSuchMethodException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (IllegalAccessException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (InvocationTargetException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      }
-      if (statsInterceptor != null) {
-        // First interceptor runs last (see ClientInterceptors.intercept()), so that no
-        // other interceptor can override the tracer factory we set in CallOptions.
-        effectiveInterceptors.add(0, statsInterceptor);
-      }
-    }
-    if (tracingEnabled) {
-      temporarilyDisableRetry = true;
-      ClientInterceptor tracingInterceptor = null;
-      try {
-        Class<?> censusTracingAccessor =
-            Class.forName("io.grpc.census.InternalCensusTracingAccessor");
-        Method getClientInterceptroMethod =
-            censusTracingAccessor.getDeclaredMethod("getClientInterceptor");
-        tracingInterceptor = (ClientInterceptor) getClientInterceptroMethod.invoke(null);
-      } catch (ClassNotFoundException e) {
-        // Replace these separate catch statements with multicatch when Android min-API >= 19
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (NoSuchMethodException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (IllegalAccessException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      } catch (InvocationTargetException e) {
-        log.log(Level.FINE, "Unable to apply census stats", e);
-      }
-      if (tracingInterceptor != null) {
-        effectiveInterceptors.add(0, tracingInterceptor);
-      }
-    }
-    return effectiveInterceptors;
-  }
-
-  /**
-   * Subclasses should override this method to provide the {@link ClientTransportFactory}
-   * appropriate for this channel. This method is meant for Transport implementors and should not
-   * be used by normal users.
-   */
-  protected abstract ClientTransportFactory buildTransportFactory();
-
-  /**
-   * Subclasses can override this method to provide a default port to {@link NameResolver} for use
-   * in cases where the target string doesn't include a port.  The default implementation returns
-   * {@link GrpcUtil#DEFAULT_PORT_SSL}.
-   */
-  protected int getDefaultPort() {
-    return GrpcUtil.DEFAULT_PORT_SSL;
-  }
-
-  /**
-   * Returns a {@link NameResolver.Factory} for the channel.
-   */
-  NameResolver.Factory getNameResolverFactory() {
-    if (authorityOverride == null) {
-      return nameResolverFactory;
-    } else {
-      return new OverrideAuthorityNameResolverFactory(nameResolverFactory, authorityOverride);
-    }
-  }
-
-  private static class DirectAddressNameResolverFactory extends NameResolver.Factory {
-    final SocketAddress address;
-    final String authority;
-
-    DirectAddressNameResolverFactory(SocketAddress address, String authority) {
-      this.address = address;
-      this.authority = authority;
-    }
-
-    @Override
-    public NameResolver newNameResolver(URI notUsedUri, NameResolver.Args args) {
-      return new NameResolver() {
-        @Override
-        public String getServiceAuthority() {
-          return authority;
-        }
-
-        @Override
-        public void start(Listener2 listener) {
-          listener.onResult(
-              ResolutionResult.newBuilder()
-                  .setAddresses(Collections.singletonList(new EquivalentAddressGroup(address)))
-                  .setAttributes(Attributes.EMPTY)
-                  .build());
-        }
-
-        @Override
-        public void shutdown() {}
-      };
-    }
-
-    @Override
-    public String getDefaultScheme() {
-      return DIRECT_ADDRESS_SCHEME;
-    }
-  }
-
-  /**
-   * Returns the correctly typed version of the builder.
-   */
-  private T thisT() {
-    @SuppressWarnings("unchecked")
-    T thisT = (T) this;
-    return thisT;
-  }
-
-  /**
-   * Returns the internal offload executor pool for offloading tasks.
-   */
-  protected ObjectPool<? extends Executor> getOffloadExecutorPool() {
-    return this.offloadExecutorPool;
-  }
-}
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
index 1bd42c0..d2bc87c 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -44,6 +44,7 @@
 import io.grpc.Context;
 import io.grpc.DecompressorRegistry;
 import io.grpc.EquivalentAddressGroup;
+import io.grpc.ForwardingChannelBuilder;
 import io.grpc.InternalChannelz;
 import io.grpc.InternalChannelz.ChannelStats;
 import io.grpc.InternalChannelz.ChannelTrace;
@@ -72,6 +73,8 @@
 import io.grpc.SynchronizationContext.ScheduledHandle;
 import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer;
 import io.grpc.internal.ClientCallImpl.ClientStreamProvider;
+import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider;
+import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder;
 import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo;
 import io.grpc.internal.RetriableStream.ChannelBufferMeter;
 import io.grpc.internal.RetriableStream.Throttle;
@@ -574,7 +577,7 @@
   private final Rescheduler idleTimer;
 
   ManagedChannelImpl(
-      AbstractManagedChannelImplBuilder<?> builder,
+      ManagedChannelImplBuilder builder,
       ClientTransportFactory clientTransportFactory,
       BackoffPolicy.Provider backoffPolicyProvider,
       ObjectPool<? extends Executor> balancerRpcExecutorPool,
@@ -661,7 +664,7 @@
     } else {
       checkArgument(
           builder.idleTimeoutMillis
-              >= AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
+              >= ManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
           "invalid idleTimeoutMillis %s", builder.idleTimeoutMillis);
       this.idleTimeoutMillis = builder.idleTimeoutMillis;
     }
@@ -1446,28 +1449,27 @@
     @Override
     public ManagedChannelBuilder<?> createResolvingOobChannelBuilder(String target) {
       final class ResolvingOobChannelBuilder
-          extends AbstractManagedChannelImplBuilder<ResolvingOobChannelBuilder> {
-        int defaultPort = -1;
+          extends ForwardingChannelBuilder<ResolvingOobChannelBuilder> {
+        private final ManagedChannelImplBuilder managedChannelImplBuilder;
 
         ResolvingOobChannelBuilder(String target) {
-          super(target);
+          managedChannelImplBuilder = new ManagedChannelImplBuilder(target,
+              new UnsupportedClientTransportFactoryBuilder(),
+              new FixedPortProvider(nameResolverArgs.getDefaultPort()));
+          managedChannelImplBuilder.executorPool = executorPool;
+          managedChannelImplBuilder.offloadExecutorPool = offloadExecutorHolder.pool;
         }
 
         @Override
-        public int getDefaultPort() {
-          return defaultPort;
-        }
-
-        @Override
-        protected ClientTransportFactory buildTransportFactory() {
-          throw new UnsupportedOperationException();
+        protected ManagedChannelBuilder<?> delegate() {
+          return managedChannelImplBuilder;
         }
 
         @Override
         public ManagedChannel build() {
           // TODO(creamsoup) prevent main channel to shutdown if oob channel is not terminated
           return new ManagedChannelImpl(
-                  this,
+                  managedChannelImplBuilder,
                   transportFactory,
                   backoffPolicyProvider,
                   balancerRpcExecutorPool,
@@ -1479,17 +1481,15 @@
 
       checkState(!terminated, "Channel is terminated");
 
-      ResolvingOobChannelBuilder builder = new ResolvingOobChannelBuilder(target);
-      builder.offloadExecutorPool = offloadExecutorHolder.pool;
-      builder.overrideAuthority(getAuthority());
       @SuppressWarnings("deprecation")
-      ResolvingOobChannelBuilder unused = builder.nameResolverFactory(nameResolverFactory);
-      builder.executorPool = executorPool;
-      builder.maxTraceEvents = maxTraceEvents;
-      builder.proxyDetector = nameResolverArgs.getProxyDetector();
-      builder.defaultPort = nameResolverArgs.getDefaultPort();
-      builder.userAgent = userAgent;
-      return builder;
+      ResolvingOobChannelBuilder builder = new ResolvingOobChannelBuilder(target)
+          .nameResolverFactory(nameResolverFactory);
+
+      return builder
+          .overrideAuthority(getAuthority())
+          .maxTraceEvents(maxTraceEvents)
+          .proxyDetector(nameResolverArgs.getProxyDetector())
+          .userAgent(userAgent);
     }
 
     @Override
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
index d2807df..202055f 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
@@ -16,19 +16,148 @@
 
 package io.grpc.internal;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog;
+import io.grpc.ClientInterceptor;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.ManagedChannel;
 import io.grpc.ManagedChannelBuilder;
+import io.grpc.NameResolver;
+import io.grpc.NameResolverRegistry;
+import io.grpc.ProxyDetector;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.annotation.Nullable;
 
 /**
  * Default managed channel builder, for usage in Transport implementations.
  */
 public final class ManagedChannelImplBuilder
-    extends AbstractManagedChannelImplBuilder<ManagedChannelImplBuilder> {
+    extends ManagedChannelBuilder<ManagedChannelImplBuilder> {
+  private static final String DIRECT_ADDRESS_SCHEME = "directaddress";
+
+  private static final Logger log = Logger.getLogger(ManagedChannelImplBuilder.class.getName());
+
+  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
+    throw new UnsupportedOperationException(
+        "ClientTransportFactoryBuilder is required, use a constructor");
+  }
+
+  public static ManagedChannelBuilder<?> forTarget(String target) {
+    throw new UnsupportedOperationException(
+        "ClientTransportFactoryBuilder is required, use a constructor");
+  }
+
+  /**
+   * An idle timeout larger than this would disable idle mode.
+   */
+  @VisibleForTesting
+  static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
+
+  /**
+   * The default idle timeout.
+   */
+  @VisibleForTesting
+  static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
+
+  /**
+   * An idle timeout smaller than this would be capped to it.
+   */
+  static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
+
+  private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
+      SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
+
+  private static final DecompressorRegistry DEFAULT_DECOMPRESSOR_REGISTRY =
+      DecompressorRegistry.getDefaultInstance();
+
+  private static final CompressorRegistry DEFAULT_COMPRESSOR_REGISTRY =
+      CompressorRegistry.getDefaultInstance();
+
+  private static final long DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24;  // 16M
+  private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M
+
+  ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
+
+  ObjectPool<? extends Executor> offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
+
+  private final List<ClientInterceptor> interceptors = new ArrayList<>();
+  final NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
+
+  // Access via getter, which may perform authority override as needed
+  private NameResolver.Factory nameResolverFactory = nameResolverRegistry.asFactory();
+
+  final String target;
+
+  @Nullable
+  private final SocketAddress directServerAddress;
+
+  @Nullable
+  String userAgent;
+
+  @Nullable
+  private String authorityOverride;
+
+  String defaultLbPolicy = GrpcUtil.DEFAULT_LB_POLICY;
+
+  boolean fullStreamDecompression;
+
+  DecompressorRegistry decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+
+  CompressorRegistry compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+
+  long idleTimeoutMillis = IDLE_MODE_DEFAULT_TIMEOUT_MILLIS;
+
+  int maxRetryAttempts = 5;
+  int maxHedgedAttempts = 5;
+  long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES;
+  long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES;
+  boolean retryEnabled = false; // TODO(zdapeng): default to true
+  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
+  // what should be the desired behavior for retry + stats/tracing.
+  // TODO(zdapeng): delete me
+  boolean temporarilyDisableRetry;
+
+  InternalChannelz channelz = InternalChannelz.instance();
+  int maxTraceEvents;
+
+  @Nullable
+  Map<String, ?> defaultServiceConfig;
+  boolean lookUpServiceConfig = true;
+
+  @Nullable
+  BinaryLog binlog;
+
+  @Nullable
+  ProxyDetector proxyDetector;
 
   private boolean authorityCheckerDisabled;
+  private boolean statsEnabled = true;
+  private boolean recordStartedRpcs = true;
+  private boolean recordFinishedRpcs = true;
+  private boolean recordRealTimeMetrics = false;
+  private boolean tracingEnabled = true;
 
   /**
    * An interface for Transport implementors to provide the {@link ClientTransportFactory}
@@ -39,6 +168,17 @@
   }
 
   /**
+   * Convenience ClientTransportFactoryBuilder, throws UnsupportedOperationException().
+   */
+  public static class UnsupportedClientTransportFactoryBuilder implements
+      ClientTransportFactoryBuilder {
+    @Override
+    public ClientTransportFactory buildClientTransportFactory() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  /**
    * An interface for Transport implementors to provide a default port to {@link
    * io.grpc.NameResolver} for use in cases where the target string doesn't include a port. The
    * default implementation returns {@link GrpcUtil#DEFAULT_PORT_SSL}.
@@ -63,11 +203,11 @@
     }
   }
 
-  private final class ManagedChannelDefaultPortProvider implements
+  private static final class ManagedChannelDefaultPortProvider implements
       ChannelBuilderDefaultPortProvider {
     @Override
     public int getDefaultPort() {
-      return ManagedChannelImplBuilder.super.getDefaultPort();
+      return GrpcUtil.DEFAULT_PORT_SSL;
     }
   }
 
@@ -82,9 +222,10 @@
   public ManagedChannelImplBuilder(String target,
       ClientTransportFactoryBuilder clientTransportFactoryBuilder,
       @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
-    super(target);
+    this.target = Preconditions.checkNotNull(target, "target");
     this.clientTransportFactoryBuilder = Preconditions
         .checkNotNull(clientTransportFactoryBuilder, "clientTransportFactoryBuilder");
+    this.directServerAddress = null;
 
     if (channelBuilderDefaultPortProvider != null) {
       this.channelBuilderDefaultPortProvider = channelBuilderDefaultPortProvider;
@@ -94,6 +235,21 @@
   }
 
   /**
+   * Returns a target string for the SocketAddress. It is only used as a placeholder, because
+   * DirectAddressNameResolverFactory will not actually try to use it. However, it must be a valid
+   * URI.
+   */
+  @VisibleForTesting
+  static String makeTargetStringForDirectAddress(SocketAddress address) {
+    try {
+      return new URI(DIRECT_ADDRESS_SCHEME, "", "/" + address, null).toString();
+    } catch (URISyntaxException e) {
+      // It should not happen.
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
    * Creates a new managed channel builder with the given server address, authority string of the
    * channel. Transport implementors must provide client transport factory builder, and may set
    * custom channel default port provider.
@@ -101,9 +257,11 @@
   public ManagedChannelImplBuilder(SocketAddress directServerAddress, String authority,
       ClientTransportFactoryBuilder clientTransportFactoryBuilder,
       @Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
-    super(directServerAddress, authority);
+    this.target = makeTargetStringForDirectAddress(directServerAddress);
     this.clientTransportFactoryBuilder = Preconditions
         .checkNotNull(clientTransportFactoryBuilder, "clientTransportFactoryBuilder");
+    this.directServerAddress = directServerAddress;
+    this.nameResolverFactory = new DirectAddressNameResolverFactory(directServerAddress, authority);
 
     if (channelBuilderDefaultPortProvider != null) {
       this.channelBuilderDefaultPortProvider = channelBuilderDefaultPortProvider;
@@ -113,13 +271,306 @@
   }
 
   @Override
-  protected ClientTransportFactory buildTransportFactory() {
-    return clientTransportFactoryBuilder.buildClientTransportFactory();
+  public ManagedChannelImplBuilder directExecutor() {
+    return executor(MoreExecutors.directExecutor());
   }
 
   @Override
-  protected int getDefaultPort() {
-    return channelBuilderDefaultPortProvider.getDefaultPort();
+  public ManagedChannelImplBuilder executor(Executor executor) {
+    if (executor != null) {
+      this.executorPool = new FixedObjectPool<>(executor);
+    } else {
+      this.executorPool = DEFAULT_EXECUTOR_POOL;
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder offloadExecutor(Executor executor) {
+    if (executor != null) {
+      this.offloadExecutorPool = new FixedObjectPool<>(executor);
+    } else {
+      this.offloadExecutorPool = DEFAULT_EXECUTOR_POOL;
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder intercept(List<ClientInterceptor> interceptors) {
+    this.interceptors.addAll(interceptors);
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder intercept(ClientInterceptor... interceptors) {
+    return intercept(Arrays.asList(interceptors));
+  }
+
+  @Deprecated
+  @Override
+  public ManagedChannelImplBuilder nameResolverFactory(NameResolver.Factory resolverFactory) {
+    Preconditions.checkState(directServerAddress == null,
+        "directServerAddress is set (%s), which forbids the use of NameResolverFactory",
+        directServerAddress);
+    if (resolverFactory != null) {
+      this.nameResolverFactory = resolverFactory;
+    } else {
+      this.nameResolverFactory = nameResolverRegistry.asFactory();
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder defaultLoadBalancingPolicy(String policy) {
+    Preconditions.checkState(directServerAddress == null,
+        "directServerAddress is set (%s), which forbids the use of load-balancing policy",
+        directServerAddress);
+    Preconditions.checkArgument(policy != null, "policy cannot be null");
+    this.defaultLbPolicy = policy;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder enableFullStreamDecompression() {
+    this.fullStreamDecompression = true;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder decompressorRegistry(DecompressorRegistry registry) {
+    if (registry != null) {
+      this.decompressorRegistry = registry;
+    } else {
+      this.decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder compressorRegistry(CompressorRegistry registry) {
+    if (registry != null) {
+      this.compressorRegistry = registry;
+    } else {
+      this.compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder userAgent(@Nullable String userAgent) {
+    this.userAgent = userAgent;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder overrideAuthority(String authority) {
+    this.authorityOverride = checkAuthority(authority);
+    return this;
+  }
+
+  @Nullable
+  @VisibleForTesting
+  String getOverrideAuthority() {
+    return authorityOverride;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder idleTimeout(long value, TimeUnit unit) {
+    checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
+    // We convert to the largest unit to avoid overflow
+    if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
+      // This disables idle mode
+      this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
+    } else {
+      this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
+    }
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder maxRetryAttempts(int maxRetryAttempts) {
+    this.maxRetryAttempts = maxRetryAttempts;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder maxHedgedAttempts(int maxHedgedAttempts) {
+    this.maxHedgedAttempts = maxHedgedAttempts;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder retryBufferSize(long bytes) {
+    checkArgument(bytes > 0L, "retry buffer size must be positive");
+    retryBufferSize = bytes;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder perRpcBufferLimit(long bytes) {
+    checkArgument(bytes > 0L, "per RPC buffer limit must be positive");
+    perRpcBufferLimit = bytes;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder disableRetry() {
+    retryEnabled = false;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder enableRetry() {
+    retryEnabled = true;
+    statsEnabled = false;
+    tracingEnabled = false;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder setBinaryLog(BinaryLog binlog) {
+    this.binlog = binlog;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder maxTraceEvents(int maxTraceEvents) {
+    checkArgument(maxTraceEvents >= 0, "maxTraceEvents must be non-negative");
+    this.maxTraceEvents = maxTraceEvents;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder proxyDetector(@Nullable ProxyDetector proxyDetector) {
+    this.proxyDetector = proxyDetector;
+    return this;
+  }
+
+  @Override
+  public ManagedChannelImplBuilder defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
+    // TODO(notcarl): use real parsing
+    defaultServiceConfig = checkMapEntryTypes(serviceConfig);
+    return this;
+  }
+
+  @Nullable
+  private static Map<String, ?> checkMapEntryTypes(@Nullable Map<?, ?> map) {
+    if (map == null) {
+      return null;
+    }
+    // Not using ImmutableMap.Builder because of extra guava dependency for Android.
+    Map<String, Object> parsedMap = new LinkedHashMap<>();
+    for (Map.Entry<?, ?> entry : map.entrySet()) {
+      checkArgument(
+          entry.getKey() instanceof String,
+          "The key of the entry '%s' is not of String type", entry);
+
+      String key = (String) entry.getKey();
+      Object value = entry.getValue();
+      if (value == null) {
+        parsedMap.put(key, null);
+      } else if (value instanceof Map) {
+        parsedMap.put(key, checkMapEntryTypes((Map<?, ?>) value));
+      } else if (value instanceof List) {
+        parsedMap.put(key, checkListEntryTypes((List<?>) value));
+      } else if (value instanceof String) {
+        parsedMap.put(key, value);
+      } else if (value instanceof Double) {
+        parsedMap.put(key, value);
+      } else if (value instanceof Boolean) {
+        parsedMap.put(key, value);
+      } else {
+        throw new IllegalArgumentException(
+            "The value of the map entry '" + entry + "' is of type '" + value.getClass()
+                + "', which is not supported");
+      }
+    }
+    return Collections.unmodifiableMap(parsedMap);
+  }
+
+  private static List<?> checkListEntryTypes(List<?> list) {
+    List<Object> parsedList = new ArrayList<>(list.size());
+    for (Object value : list) {
+      if (value == null) {
+        parsedList.add(null);
+      } else if (value instanceof Map) {
+        parsedList.add(checkMapEntryTypes((Map<?, ?>) value));
+      } else if (value instanceof List) {
+        parsedList.add(checkListEntryTypes((List<?>) value));
+      } else if (value instanceof String) {
+        parsedList.add(value);
+      } else if (value instanceof Double) {
+        parsedList.add(value);
+      } else if (value instanceof Boolean) {
+        parsedList.add(value);
+      } else {
+        throw new IllegalArgumentException(
+            "The entry '" + value + "' is of type '" + value.getClass()
+                + "', which is not supported");
+      }
+    }
+    return Collections.unmodifiableList(parsedList);
+  }
+
+  @Override
+  public ManagedChannelImplBuilder disableServiceConfigLookUp() {
+    this.lookUpServiceConfig = false;
+    return this;
+  }
+
+  /**
+   * Disable or enable stats features. Enabled by default.
+   *
+   * <p>For the current release, calling {@code setStatsEnabled(true)} may have a side effect that
+   * disables retry.
+   */
+  public void setStatsEnabled(boolean value) {
+    statsEnabled = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC upstarts.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  public void setStatsRecordStartedRpcs(boolean value) {
+    recordStartedRpcs = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC completions.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  public void setStatsRecordFinishedRpcs(boolean value) {
+    recordFinishedRpcs = value;
+  }
+
+  /**
+   * Disable or enable real-time metrics recording.  Effective only if {@link #setStatsEnabled} is
+   * set to true.  Disabled by default.
+   */
+  public void setStatsRecordRealTimeMetrics(boolean value) {
+    recordRealTimeMetrics = value;
+  }
+
+  /**
+   * Disable or enable tracing features.  Enabled by default.
+   *
+   * <p>For the current release, calling {@code setTracingEnabled(true)} may have a side effect that
+   * disables retry.
+   */
+  public void setTracingEnabled(boolean value) {
+    tracingEnabled = value;
+  }
+
+  /**
+   * Verifies the authority is valid.
+   */
+  @VisibleForTesting
+  String checkAuthority(String authority) {
+    if (authorityCheckerDisabled) {
+      return authority;
+    }
+    return GrpcUtil.checkAuthority(authority);
   }
 
   /** Disable the check whether the authority is valid. */
@@ -135,48 +586,146 @@
   }
 
   @Override
-  protected String checkAuthority(String authority) {
-    if (authorityCheckerDisabled) {
-      return authority;
+  public ManagedChannel build() {
+    return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
+        this,
+        clientTransportFactoryBuilder.buildClientTransportFactory(),
+        new ExponentialBackoffPolicy.Provider(),
+        SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR),
+        GrpcUtil.STOPWATCH_SUPPLIER,
+        getEffectiveInterceptors(),
+        TimeProvider.SYSTEM_TIME_PROVIDER));
+  }
+
+  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
+  // what should be the desired behavior for retry + stats/tracing.
+  // TODO(zdapeng): FIX IT
+  @VisibleForTesting
+  List<ClientInterceptor> getEffectiveInterceptors() {
+    List<ClientInterceptor> effectiveInterceptors =
+        new ArrayList<>(this.interceptors);
+    temporarilyDisableRetry = false;
+    if (statsEnabled) {
+      temporarilyDisableRetry = true;
+      ClientInterceptor statsInterceptor = null;
+      try {
+        Class<?> censusStatsAccessor =
+            Class.forName("io.grpc.census.InternalCensusStatsAccessor");
+        Method getClientInterceptorMethod =
+            censusStatsAccessor.getDeclaredMethod(
+                "getClientInterceptor",
+                boolean.class,
+                boolean.class,
+                boolean.class);
+        statsInterceptor =
+            (ClientInterceptor) getClientInterceptorMethod
+                .invoke(
+                    null,
+                    recordStartedRpcs,
+                    recordFinishedRpcs,
+                    recordRealTimeMetrics);
+      } catch (ClassNotFoundException e) {
+        // Replace these separate catch statements with multicatch when Android min-API >= 19
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (NoSuchMethodException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (IllegalAccessException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (InvocationTargetException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      }
+      if (statsInterceptor != null) {
+        // First interceptor runs last (see ClientInterceptors.intercept()), so that no
+        // other interceptor can override the tracer factory we set in CallOptions.
+        effectiveInterceptors.add(0, statsInterceptor);
+      }
     }
-    return super.checkAuthority(authority);
+    if (tracingEnabled) {
+      temporarilyDisableRetry = true;
+      ClientInterceptor tracingInterceptor = null;
+      try {
+        Class<?> censusTracingAccessor =
+            Class.forName("io.grpc.census.InternalCensusTracingAccessor");
+        Method getClientInterceptroMethod =
+            censusTracingAccessor.getDeclaredMethod("getClientInterceptor");
+        tracingInterceptor = (ClientInterceptor) getClientInterceptroMethod.invoke(null);
+      } catch (ClassNotFoundException e) {
+        // Replace these separate catch statements with multicatch when Android min-API >= 19
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (NoSuchMethodException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (IllegalAccessException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      } catch (InvocationTargetException e) {
+        log.log(Level.FINE, "Unable to apply census stats", e);
+      }
+      if (tracingInterceptor != null) {
+        effectiveInterceptors.add(0, tracingInterceptor);
+      }
+    }
+    return effectiveInterceptors;
   }
 
-  @Override
-  public void setStatsEnabled(boolean value) {
-    super.setStatsEnabled(value);
+  /**
+   * Returns a default port to {@link NameResolver} for use in cases where the target string doesn't
+   * include a port. The default implementation returns {@link GrpcUtil#DEFAULT_PORT_SSL}.
+   */
+  int getDefaultPort() {
+    return channelBuilderDefaultPortProvider.getDefaultPort();
   }
 
-  @Override
-  public void setStatsRecordStartedRpcs(boolean value) {
-    super.setStatsRecordStartedRpcs(value);
+  /**
+   * Returns a {@link NameResolver.Factory} for the channel.
+   */
+  NameResolver.Factory getNameResolverFactory() {
+    if (authorityOverride == null) {
+      return nameResolverFactory;
+    } else {
+      return new OverrideAuthorityNameResolverFactory(nameResolverFactory, authorityOverride);
+    }
   }
 
-  @Override
-  public void setStatsRecordFinishedRpcs(boolean value) {
-    super.setStatsRecordFinishedRpcs(value);
+  private static class DirectAddressNameResolverFactory extends NameResolver.Factory {
+    final SocketAddress address;
+    final String authority;
+
+    DirectAddressNameResolverFactory(SocketAddress address, String authority) {
+      this.address = address;
+      this.authority = authority;
+    }
+
+    @Override
+    public NameResolver newNameResolver(URI notUsedUri, NameResolver.Args args) {
+      return new NameResolver() {
+        @Override
+        public String getServiceAuthority() {
+          return authority;
+        }
+
+        @Override
+        public void start(Listener2 listener) {
+          listener.onResult(
+              ResolutionResult.newBuilder()
+                  .setAddresses(Collections.singletonList(new EquivalentAddressGroup(address)))
+                  .setAttributes(Attributes.EMPTY)
+                  .build());
+        }
+
+        @Override
+        public void shutdown() {}
+      };
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      return DIRECT_ADDRESS_SCHEME;
+    }
   }
 
-  @Override
-  public void setStatsRecordRealTimeMetrics(boolean value) {
-    super.setStatsRecordRealTimeMetrics(value);
-  }
-
-  @Override
-  public void setTracingEnabled(boolean value) {
-    super.setTracingEnabled(value);
-  }
-
-  @Override
+  /**
+   * Returns the internal offload executor pool for offloading tasks.
+   */
   public ObjectPool<? extends Executor> getOffloadExecutorPool() {
-    return super.getOffloadExecutorPool();
-  }
-
-  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
-    throw new UnsupportedOperationException("ClientTransportFactoryBuilder is required");
-  }
-
-  public static ManagedChannelBuilder<?> forTarget(String target) {
-    throw new UnsupportedOperationException("ClientTransportFactoryBuilder is required");
+    return this.offloadExecutorPool;
   }
 }
diff --git a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java
deleted file mode 100644
index 2ac49c4..0000000
--- a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright 2016 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.internal;
-
-import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.TestCase.assertFalse;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-
-import com.google.common.util.concurrent.MoreExecutors;
-import io.grpc.CallOptions;
-import io.grpc.Channel;
-import io.grpc.ClientCall;
-import io.grpc.ClientInterceptor;
-import io.grpc.CompressorRegistry;
-import io.grpc.DecompressorRegistry;
-import io.grpc.MethodDescriptor;
-import io.grpc.NameResolver;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@link AbstractManagedChannelImplBuilder}. */
-@RunWith(JUnit4.class)
-public class AbstractManagedChannelImplBuilderTest {
-
-  @Rule
-  public final ExpectedException thrown = ExpectedException.none();
-
-  private static final ClientInterceptor DUMMY_USER_INTERCEPTOR =
-      new ClientInterceptor() {
-        @Override
-        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
-            MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
-          return next.newCall(method, callOptions);
-        }
-      };
-
-  private final Builder builder = new Builder("fake");
-  private final Builder directAddressBuilder = new Builder(new SocketAddress(){}, "fake");
-
-  @Test
-  public void executor_default() {
-    assertNotNull(builder.executorPool);
-  }
-
-  @Test
-  public void executor_normal() {
-    Executor executor = mock(Executor.class);
-    assertEquals(builder, builder.executor(executor));
-    assertEquals(executor, builder.executorPool.getObject());
-  }
-
-  @Test
-  public void executor_null() {
-    ObjectPool<? extends Executor> defaultValue = builder.executorPool;
-    builder.executor(mock(Executor.class));
-    assertEquals(builder, builder.executor(null));
-    assertEquals(defaultValue, builder.executorPool);
-  }
-
-  @Test
-  public void directExecutor() {
-    assertEquals(builder, builder.directExecutor());
-    assertEquals(MoreExecutors.directExecutor(), builder.executorPool.getObject());
-  }
-
-  @Test
-  public void offloadExecutor_normal() {
-    Executor executor = mock(Executor.class);
-    assertEquals(builder, builder.offloadExecutor(executor));
-    assertEquals(executor, builder.offloadExecutorPool.getObject());
-  }
-
-  @Test
-  public void offloadExecutor_null() {
-    ObjectPool<? extends Executor> defaultValue = builder.offloadExecutorPool;
-    builder.offloadExecutor(mock(Executor.class));
-    assertEquals(builder, builder.offloadExecutor(null));
-    assertEquals(defaultValue, builder.offloadExecutorPool);
-  }
-
-  @Test
-  public void nameResolverFactory_default() {
-    assertNotNull(builder.getNameResolverFactory());
-  }
-
-  @Test
-  @SuppressWarnings("deprecation")
-  public void nameResolverFactory_normal() {
-    NameResolver.Factory nameResolverFactory = mock(NameResolver.Factory.class);
-    assertEquals(builder, builder.nameResolverFactory(nameResolverFactory));
-    assertEquals(nameResolverFactory, builder.getNameResolverFactory());
-  }
-
-  @Test
-  @SuppressWarnings("deprecation")
-  public void nameResolverFactory_null() {
-    NameResolver.Factory defaultValue = builder.getNameResolverFactory();
-    builder.nameResolverFactory(mock(NameResolver.Factory.class));
-    assertEquals(builder, builder.nameResolverFactory(null));
-    assertEquals(defaultValue, builder.getNameResolverFactory());
-  }
-
-  @Test(expected = IllegalStateException.class)
-  @SuppressWarnings("deprecation")
-  public void nameResolverFactory_notAllowedWithDirectAddress() {
-    directAddressBuilder.nameResolverFactory(mock(NameResolver.Factory.class));
-  }
-
-  @Test
-  public void defaultLoadBalancingPolicy_default() {
-    assertEquals("pick_first", builder.defaultLbPolicy);
-  }
-
-  @Test
-  public void defaultLoadBalancingPolicy_normal() {
-    assertEquals(builder, builder.defaultLoadBalancingPolicy("magic_balancer"));
-    assertEquals("magic_balancer", builder.defaultLbPolicy);
-  }
-
-  @Test(expected = IllegalArgumentException.class)
-  public void defaultLoadBalancingPolicy_null() {
-    builder.defaultLoadBalancingPolicy(null);
-  }
-
-  @Test(expected = IllegalStateException.class)
-  public void defaultLoadBalancingPolicy_notAllowedWithDirectAddress() {
-    directAddressBuilder.defaultLoadBalancingPolicy("magic_balancer");
-  }
-
-  @Test
-  public void fullStreamDecompression_default() {
-    assertFalse(builder.fullStreamDecompression);
-  }
-
-  @Test
-  public void fullStreamDecompression_enabled() {
-    assertEquals(builder, builder.enableFullStreamDecompression());
-    assertTrue(builder.fullStreamDecompression);
-  }
-
-  @Test
-  public void decompressorRegistry_default() {
-    assertNotNull(builder.decompressorRegistry);
-  }
-
-  @Test
-  public void decompressorRegistry_normal() {
-    DecompressorRegistry decompressorRegistry = DecompressorRegistry.emptyInstance();
-    assertNotEquals(decompressorRegistry, builder.decompressorRegistry);
-    assertEquals(builder, builder.decompressorRegistry(decompressorRegistry));
-    assertEquals(decompressorRegistry, builder.decompressorRegistry);
-  }
-
-  @Test
-  public void decompressorRegistry_null() {
-    DecompressorRegistry defaultValue = builder.decompressorRegistry;
-    assertEquals(builder, builder.decompressorRegistry(DecompressorRegistry.emptyInstance()));
-    assertNotEquals(defaultValue, builder.decompressorRegistry);
-    builder.decompressorRegistry(null);
-    assertEquals(defaultValue, builder.decompressorRegistry);
-  }
-
-  @Test
-  public void compressorRegistry_default() {
-    assertNotNull(builder.compressorRegistry);
-  }
-
-  @Test
-  public void compressorRegistry_normal() {
-    CompressorRegistry compressorRegistry = CompressorRegistry.newEmptyInstance();
-    assertNotEquals(compressorRegistry, builder.compressorRegistry);
-    assertEquals(builder, builder.compressorRegistry(compressorRegistry));
-    assertEquals(compressorRegistry, builder.compressorRegistry);
-  }
-
-  @Test
-  public void compressorRegistry_null() {
-    CompressorRegistry defaultValue = builder.compressorRegistry;
-    builder.compressorRegistry(CompressorRegistry.newEmptyInstance());
-    assertNotEquals(defaultValue, builder.compressorRegistry);
-    assertEquals(builder, builder.compressorRegistry(null));
-    assertEquals(defaultValue, builder.compressorRegistry);
-  }
-
-  @Test
-  public void userAgent_default() {
-    assertNull(builder.userAgent);
-  }
-
-  @Test
-  public void userAgent_normal() {
-    String userAgent = "user-agent/1";
-    assertEquals(builder, builder.userAgent(userAgent));
-    assertEquals(userAgent, builder.userAgent);
-  }
-
-  @Test
-  public void userAgent_null() {
-    assertEquals(builder, builder.userAgent(null));
-    assertNull(builder.userAgent);
-
-    builder.userAgent("user-agent/1");
-    builder.userAgent(null);
-    assertNull(builder.userAgent);
-  }
-
-  @Test
-  public void overrideAuthority_default() {
-    assertNull(builder.authorityOverride);
-  }
-
-  @Test
-  public void overrideAuthority_normal() {
-    String overrideAuthority = "best-authority";
-    assertEquals(builder, builder.overrideAuthority(overrideAuthority));
-    assertEquals(overrideAuthority, builder.authorityOverride);
-  }
-
-  @Test(expected = NullPointerException.class)
-  public void overrideAuthority_null() {
-    builder.overrideAuthority(null);
-  }
-
-  @Test(expected = IllegalArgumentException.class)
-  public void overrideAuthority_invalid() {
-    builder.overrideAuthority("not_allowed");
-  }
-
-  @Test
-  public void overrideAuthority_getNameResolverFactory() {
-    assertNull(builder.authorityOverride);
-    assertFalse(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
-    builder.overrideAuthority("google.com");
-    assertTrue(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
-  }
-
-  @Test
-  public void makeTargetStringForDirectAddress_scopedIpv6() throws Exception {
-    InetSocketAddress address = new InetSocketAddress("0:0:0:0:0:0:0:0%0", 10005);
-    assertEquals("/0:0:0:0:0:0:0:0%0:10005", address.toString());
-    String target = AbstractManagedChannelImplBuilder.makeTargetStringForDirectAddress(address);
-    URI uri = new URI(target);
-    assertEquals("directaddress:////0:0:0:0:0:0:0:0%250:10005", target);
-    assertEquals(target, uri.toString());
-  }
-
-  @Test
-  public void getEffectiveInterceptors_default() {
-    builder.intercept(DUMMY_USER_INTERCEPTOR);
-    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
-    assertEquals(3, effectiveInterceptors.size());
-    assertThat(effectiveInterceptors.get(0).getClass().getName())
-        .isEqualTo("io.grpc.census.CensusTracingModule$TracingClientInterceptor");
-    assertThat(effectiveInterceptors.get(1).getClass().getName())
-        .isEqualTo("io.grpc.census.CensusStatsModule$StatsClientInterceptor");
-    assertThat(effectiveInterceptors.get(2)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
-  }
-
-  @Test
-  public void getEffectiveInterceptors_disableStats() {
-    builder.intercept(DUMMY_USER_INTERCEPTOR);
-    builder.setStatsEnabled(false);
-    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
-    assertEquals(2, effectiveInterceptors.size());
-    assertThat(effectiveInterceptors.get(0).getClass().getName())
-        .isEqualTo("io.grpc.census.CensusTracingModule$TracingClientInterceptor");
-    assertThat(effectiveInterceptors.get(1)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
-  }
-
-  @Test
-  public void getEffectiveInterceptors_disableTracing() {
-    builder.intercept(DUMMY_USER_INTERCEPTOR);
-    builder.setTracingEnabled(false);
-    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
-    assertEquals(2, effectiveInterceptors.size());
-    assertThat(effectiveInterceptors.get(0).getClass().getName())
-        .isEqualTo("io.grpc.census.CensusStatsModule$StatsClientInterceptor");
-    assertThat(effectiveInterceptors.get(1)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
-  }
-
-  @Test
-  public void getEffectiveInterceptors_disableBoth() {
-    builder.intercept(DUMMY_USER_INTERCEPTOR);
-    builder.setStatsEnabled(false);
-    builder.setTracingEnabled(false);
-    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
-    assertThat(effectiveInterceptors).containsExactly(DUMMY_USER_INTERCEPTOR);
-  }
-
-  @Test
-  public void idleTimeout() {
-    assertEquals(AbstractManagedChannelImplBuilder.IDLE_MODE_DEFAULT_TIMEOUT_MILLIS,
-        builder.getIdleTimeoutMillis());
-
-    builder.idleTimeout(Long.MAX_VALUE, TimeUnit.DAYS);
-    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.getIdleTimeoutMillis());
-
-    builder.idleTimeout(AbstractManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS,
-        TimeUnit.DAYS);
-    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.getIdleTimeoutMillis());
-
-    try {
-      builder.idleTimeout(0, TimeUnit.SECONDS);
-      fail("Should throw");
-    } catch (IllegalArgumentException e) {
-      // expected
-    }
-
-    builder.idleTimeout(1, TimeUnit.NANOSECONDS);
-    assertEquals(AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
-        builder.getIdleTimeoutMillis());
-
-    builder.idleTimeout(30, TimeUnit.SECONDS);
-    assertEquals(TimeUnit.SECONDS.toMillis(30), builder.getIdleTimeoutMillis());
-  }
-
-  @Test
-  public void maxRetryAttempts() {
-    assertEquals(5, builder.maxRetryAttempts);
-
-    builder.maxRetryAttempts(3);
-    assertEquals(3, builder.maxRetryAttempts);
-  }
-
-  @Test
-  public void maxHedgedAttempts() {
-    assertEquals(5, builder.maxHedgedAttempts);
-
-    builder.maxHedgedAttempts(3);
-    assertEquals(3, builder.maxHedgedAttempts);
-  }
-
-  @Test
-  public void retryBufferSize() {
-    assertEquals(1L << 24, builder.retryBufferSize);
-
-    builder.retryBufferSize(3456L);
-    assertEquals(3456L, builder.retryBufferSize);
-  }
-
-  @Test
-  public void perRpcBufferLimit() {
-    assertEquals(1L << 20, builder.perRpcBufferLimit);
-
-    builder.perRpcBufferLimit(3456L);
-    assertEquals(3456L, builder.perRpcBufferLimit);
-  }
-
-  @Test
-  public void retryBufferSizeInvalidArg() {
-    thrown.expect(IllegalArgumentException.class);
-    builder.retryBufferSize(0L);
-  }
-
-  @Test
-  public void perRpcBufferLimitInvalidArg() {
-    thrown.expect(IllegalArgumentException.class);
-    builder.perRpcBufferLimit(0L);
-  }
-
-  @Test
-  public void disableRetry() {
-    builder.enableRetry();
-    assertTrue(builder.retryEnabled);
-
-    builder.disableRetry();
-    assertFalse(builder.retryEnabled);
-
-    builder.enableRetry();
-    assertTrue(builder.retryEnabled);
-
-    builder.disableRetry();
-    assertFalse(builder.retryEnabled);
-  }
-
-  @Test
-  public void defaultServiceConfig_nullKey() {
-    Map<String, Object> config = new HashMap<>();
-    config.put(null, "val");
-
-    thrown.expect(IllegalArgumentException.class);
-    builder.defaultServiceConfig(config);
-  }
-
-  @Test
-  public void defaultServiceConfig_intKey() {
-    Map<Integer, Object> subConfig = new HashMap<>();
-    subConfig.put(3, "val");
-    Map<String, Object> config = new HashMap<>();
-    config.put("key", subConfig);
-
-    thrown.expect(IllegalArgumentException.class);
-    builder.defaultServiceConfig(config);
-  }
-
-  @Test
-  public void defaultServiceConfig_intValue() {
-    Map<String, Object> config = new HashMap<>();
-    config.put("key", 3);
-
-    thrown.expect(IllegalArgumentException.class);
-    builder.defaultServiceConfig(config);
-  }
-
-  @Test
-  public void defaultServiceConfig_nested() {
-    Map<String, Object> config = new HashMap<>();
-    List<Object> list1 = new ArrayList<>();
-    list1.add(123D);
-    list1.add(null);
-    list1.add(true);
-    list1.add("str");
-    Map<String, Object> map2 = new HashMap<>();
-    map2.put("key2", false);
-    map2.put("key3", null);
-    map2.put("key4", Collections.singletonList("v4"));
-    map2.put("key4", 3.14D);
-    map2.put("key5", new HashMap<String, Object>());
-    list1.add(map2);
-    config.put("key1", list1);
-
-    builder.defaultServiceConfig(config);
-
-    assertThat(builder.defaultServiceConfig).containsExactlyEntriesIn(config);
-  }
-
-  @Test
-  public void disableNameResolverServiceConfig() {
-    assertThat(builder.lookUpServiceConfig).isTrue();
-
-    builder.disableServiceConfigLookUp();
-    assertThat(builder.lookUpServiceConfig).isFalse();
-  }
-
-  static class Builder extends AbstractManagedChannelImplBuilder<Builder> {
-    Builder(String target) {
-      super(target);
-    }
-
-    Builder(SocketAddress directServerAddress, String authority) {
-      super(directServerAddress, authority);
-    }
-
-    @Override
-    protected ClientTransportFactory buildTransportFactory() {
-      throw new UnsupportedOperationException();
-    }
-  }
-}
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java
index 5d3f4cf..dfe9d19 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplBuilderTest.java
@@ -16,14 +16,43 @@
 
 package io.grpc.internal;
 
+import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.assertFalse;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.NameResolver;
 import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider;
 import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
 import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider;
+import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder;
+import io.grpc.testing.GrpcCleanupRule;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,56 +70,295 @@
   private static final String DUMMY_TARGET = "fake-target";
   private static final String DUMMY_AUTHORITY_VALID = "valid:1234";
   private static final String DUMMY_AUTHORITY_INVALID = "[ : : 1]";
+  private static final ClientInterceptor DUMMY_USER_INTERCEPTOR =
+      new ClientInterceptor() {
+        @Override
+        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+            MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+          return next.newCall(method, callOptions);
+        }
+      };
 
   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
   @Rule public final ExpectedException thrown = ExpectedException.none();
+  @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule();
 
+  @Mock private ClientTransportFactory mockClientTransportFactory;
   @Mock private ClientTransportFactoryBuilder mockClientTransportFactoryBuilder;
-  @Mock private ChannelBuilderDefaultPortProvider mockChannelBuilderDefaultPortProvider;
+
   private ManagedChannelImplBuilder builder;
+  private ManagedChannelImplBuilder directAddressBuilder;
+  private final FakeClock clock = new FakeClock();
+
 
   @Before
   public void setUp() throws Exception {
     builder = new ManagedChannelImplBuilder(
         DUMMY_TARGET,
-        mockClientTransportFactoryBuilder,
-        mockChannelBuilderDefaultPortProvider);
-  }
-
-  /** Ensure buildTransportFactory() delegates to the custom implementation. */
-  @Test
-  public void buildTransportFactory() {
-    final ClientTransportFactory clientTransportFactory = mock(ClientTransportFactory.class);
-    when(mockClientTransportFactoryBuilder.buildClientTransportFactory())
-        .thenReturn(clientTransportFactory);
-    assertEquals(clientTransportFactory, builder.buildTransportFactory());
-    verify(mockClientTransportFactoryBuilder).buildClientTransportFactory();
+        new UnsupportedClientTransportFactoryBuilder(),
+        new FixedPortProvider(DUMMY_PORT));
+    directAddressBuilder = new ManagedChannelImplBuilder(
+        new SocketAddress() {},
+        DUMMY_TARGET,
+        new UnsupportedClientTransportFactoryBuilder(),
+        new FixedPortProvider(DUMMY_PORT));
   }
 
   /** Ensure getDefaultPort() returns default port when no custom implementation provided. */
   @Test
   public void getDefaultPort_default() {
-    final ManagedChannelImplBuilder builderNoPortProvider = new ManagedChannelImplBuilder(
-        DUMMY_TARGET, mockClientTransportFactoryBuilder, null);
-    assertEquals(GrpcUtil.DEFAULT_PORT_SSL, builderNoPortProvider.getDefaultPort());
+    builder = new ManagedChannelImplBuilder(DUMMY_TARGET,
+        new UnsupportedClientTransportFactoryBuilder(), null);
+    assertEquals(GrpcUtil.DEFAULT_PORT_SSL, builder.getDefaultPort());
   }
 
   /** Ensure getDefaultPort() delegates to the custom implementation. */
   @Test
   public void getDefaultPort_custom() {
-    when(mockChannelBuilderDefaultPortProvider.getDefaultPort()).thenReturn(DUMMY_PORT);
-    assertEquals(DUMMY_PORT, builder.getDefaultPort());
+    int customPort = 43;
+    ChannelBuilderDefaultPortProvider mockChannelBuilderDefaultPortProvider = mock(
+        ChannelBuilderDefaultPortProvider.class);
+    when(mockChannelBuilderDefaultPortProvider.getDefaultPort()).thenReturn(customPort);
+
+    builder = new ManagedChannelImplBuilder(DUMMY_TARGET,
+        new UnsupportedClientTransportFactoryBuilder(),
+        mockChannelBuilderDefaultPortProvider);
+    assertEquals(customPort, builder.getDefaultPort());
     verify(mockChannelBuilderDefaultPortProvider).getDefaultPort();
   }
 
   /** Test FixedPortProvider(int port). */
   @Test
   public void getDefaultPort_fixedPortProvider() {
-    final ManagedChannelImplBuilder builderFixedPortProvider = new ManagedChannelImplBuilder(
-        DUMMY_TARGET,
-        mockClientTransportFactoryBuilder,
-        new FixedPortProvider(DUMMY_PORT));
-    assertEquals(DUMMY_PORT, builderFixedPortProvider.getDefaultPort());
+    int fixedPort = 43;
+    builder = new ManagedChannelImplBuilder(DUMMY_TARGET,
+        new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(fixedPort));
+    assertEquals(fixedPort, builder.getDefaultPort());
+  }
+
+  @Test
+  public void executor_default() {
+    assertNotNull(builder.executorPool);
+  }
+
+  @Test
+  public void executor_normal() {
+    Executor executor = mock(Executor.class);
+    assertEquals(builder, builder.executor(executor));
+    assertEquals(executor, builder.executorPool.getObject());
+  }
+
+  @Test
+  public void executor_null() {
+    ObjectPool<? extends Executor> defaultValue = builder.executorPool;
+    builder.executor(mock(Executor.class));
+    assertEquals(builder, builder.executor(null));
+    assertEquals(defaultValue, builder.executorPool);
+  }
+
+  @Test
+  public void directExecutor() {
+    assertEquals(builder, builder.directExecutor());
+    assertEquals(MoreExecutors.directExecutor(), builder.executorPool.getObject());
+  }
+
+  @Test
+  public void offloadExecutor_normal() {
+    Executor executor = mock(Executor.class);
+    assertEquals(builder, builder.offloadExecutor(executor));
+    assertEquals(executor, builder.offloadExecutorPool.getObject());
+  }
+
+  @Test
+  public void offloadExecutor_null() {
+    ObjectPool<? extends Executor> defaultValue = builder.offloadExecutorPool;
+    builder.offloadExecutor(mock(Executor.class));
+    assertEquals(builder, builder.offloadExecutor(null));
+    assertEquals(defaultValue, builder.offloadExecutorPool);
+  }
+
+  @Test
+  public void nameResolverFactory_default() {
+    assertNotNull(builder.getNameResolverFactory());
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void nameResolverFactory_normal() {
+    NameResolver.Factory nameResolverFactory = mock(NameResolver.Factory.class);
+    assertEquals(builder, builder.nameResolverFactory(nameResolverFactory));
+    assertEquals(nameResolverFactory, builder.getNameResolverFactory());
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void nameResolverFactory_null() {
+    NameResolver.Factory defaultValue = builder.getNameResolverFactory();
+    builder.nameResolverFactory(mock(NameResolver.Factory.class));
+    assertEquals(builder, builder.nameResolverFactory(null));
+    assertEquals(defaultValue, builder.getNameResolverFactory());
+  }
+
+  @Test(expected = IllegalStateException.class)
+  @SuppressWarnings("deprecation")
+  public void nameResolverFactory_notAllowedWithDirectAddress() {
+    directAddressBuilder.nameResolverFactory(mock(NameResolver.Factory.class));
+  }
+
+  @Test
+  public void defaultLoadBalancingPolicy_default() {
+    assertEquals("pick_first", builder.defaultLbPolicy);
+  }
+
+  @Test
+  public void defaultLoadBalancingPolicy_normal() {
+    assertEquals(builder, builder.defaultLoadBalancingPolicy("magic_balancer"));
+    assertEquals("magic_balancer", builder.defaultLbPolicy);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void defaultLoadBalancingPolicy_null() {
+    builder.defaultLoadBalancingPolicy(null);
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void defaultLoadBalancingPolicy_notAllowedWithDirectAddress() {
+    directAddressBuilder.defaultLoadBalancingPolicy("magic_balancer");
+  }
+
+  @Test
+  public void fullStreamDecompression_default() {
+    assertFalse(builder.fullStreamDecompression);
+  }
+
+  @Test
+  public void fullStreamDecompression_enabled() {
+    assertEquals(builder, builder.enableFullStreamDecompression());
+    assertTrue(builder.fullStreamDecompression);
+  }
+
+  @Test
+  public void decompressorRegistry_default() {
+    assertNotNull(builder.decompressorRegistry);
+  }
+
+  @Test
+  public void decompressorRegistry_normal() {
+    DecompressorRegistry decompressorRegistry = DecompressorRegistry.emptyInstance();
+    assertNotEquals(decompressorRegistry, builder.decompressorRegistry);
+    assertEquals(builder, builder.decompressorRegistry(decompressorRegistry));
+    assertEquals(decompressorRegistry, builder.decompressorRegistry);
+  }
+
+  @Test
+  public void decompressorRegistry_null() {
+    DecompressorRegistry defaultValue = builder.decompressorRegistry;
+    assertEquals(builder, builder.decompressorRegistry(DecompressorRegistry.emptyInstance()));
+    assertNotEquals(defaultValue, builder.decompressorRegistry);
+    builder.decompressorRegistry(null);
+    assertEquals(defaultValue, builder.decompressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_default() {
+    assertNotNull(builder.compressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_normal() {
+    CompressorRegistry compressorRegistry = CompressorRegistry.newEmptyInstance();
+    assertNotEquals(compressorRegistry, builder.compressorRegistry);
+    assertEquals(builder, builder.compressorRegistry(compressorRegistry));
+    assertEquals(compressorRegistry, builder.compressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_null() {
+    CompressorRegistry defaultValue = builder.compressorRegistry;
+    builder.compressorRegistry(CompressorRegistry.newEmptyInstance());
+    assertNotEquals(defaultValue, builder.compressorRegistry);
+    assertEquals(builder, builder.compressorRegistry(null));
+    assertEquals(defaultValue, builder.compressorRegistry);
+  }
+
+  @Test
+  public void userAgent_default() {
+    assertNull(builder.userAgent);
+  }
+
+  @Test
+  public void userAgent_normal() {
+    String userAgent = "user-agent/1";
+    assertEquals(builder, builder.userAgent(userAgent));
+    assertEquals(userAgent, builder.userAgent);
+  }
+
+  @Test
+  public void userAgent_null() {
+    assertEquals(builder, builder.userAgent(null));
+    assertNull(builder.userAgent);
+
+    builder.userAgent("user-agent/1");
+    builder.userAgent(null);
+    assertNull(builder.userAgent);
+  }
+
+  @Test
+  public void authorityIsReadable_default() {
+    when(mockClientTransportFactory.getScheduledExecutorService())
+        .thenReturn(clock.getScheduledExecutorService());
+    when(mockClientTransportFactoryBuilder.buildClientTransportFactory())
+        .thenReturn(mockClientTransportFactory);
+
+    builder = new ManagedChannelImplBuilder(DUMMY_AUTHORITY_VALID,
+        mockClientTransportFactoryBuilder, new FixedPortProvider(DUMMY_PORT));
+    ManagedChannel channel = grpcCleanupRule.register(builder.build());
+    assertEquals(DUMMY_AUTHORITY_VALID, channel.authority());
+  }
+
+  @Test
+  public void authorityIsReadable_overrideAuthority() {
+    String overrideAuthority = "best-authority";
+    when(mockClientTransportFactory.getScheduledExecutorService())
+        .thenReturn(clock.getScheduledExecutorService());
+    when(mockClientTransportFactoryBuilder.buildClientTransportFactory())
+        .thenReturn(mockClientTransportFactory);
+
+    builder = new ManagedChannelImplBuilder(DUMMY_TARGET,
+        mockClientTransportFactoryBuilder, new FixedPortProvider(DUMMY_PORT))
+        .overrideAuthority(overrideAuthority);
+    ManagedChannel channel = grpcCleanupRule.register(builder.build());
+    assertEquals(overrideAuthority, channel.authority());
+  }
+
+  @Test
+  public void overrideAuthority_default() {
+    assertNull(builder.getOverrideAuthority());
+  }
+
+  @Test
+  public void overrideAuthority_normal() {
+    String overrideAuthority = "best-authority";
+    assertEquals(builder, builder.overrideAuthority(overrideAuthority));
+    assertEquals(overrideAuthority, builder.getOverrideAuthority());
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void overrideAuthority_null() {
+    builder.overrideAuthority(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void overrideAuthority_invalid() {
+    builder.overrideAuthority("not_allowed");
+  }
+
+  @Test
+  public void overrideAuthority_getNameResolverFactory() {
+    assertNull(builder.getOverrideAuthority());
+    assertFalse(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
+    builder.overrideAuthority("google.com");
+    assertTrue(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
   }
 
   @Test
@@ -132,4 +400,202 @@
     builder.disableCheckAuthority().enableCheckAuthority();
     builder.checkAuthority(DUMMY_AUTHORITY_INVALID);
   }
+
+  @Test
+  public void makeTargetStringForDirectAddress_scopedIpv6() throws Exception {
+    InetSocketAddress address = new InetSocketAddress("0:0:0:0:0:0:0:0%0", 10005);
+    assertEquals("/0:0:0:0:0:0:0:0%0:10005", address.toString());
+    String target = ManagedChannelImplBuilder.makeTargetStringForDirectAddress(address);
+    URI uri = new URI(target);
+    assertEquals("directaddress:////0:0:0:0:0:0:0:0%250:10005", target);
+    assertEquals(target, uri.toString());
+  }
+
+  @Test
+  public void getEffectiveInterceptors_default() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(3, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0).getClass().getName())
+        .isEqualTo("io.grpc.census.CensusTracingModule$TracingClientInterceptor");
+    assertThat(effectiveInterceptors.get(1).getClass().getName())
+        .isEqualTo("io.grpc.census.CensusStatsModule$StatsClientInterceptor");
+    assertThat(effectiveInterceptors.get(2)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableStats() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setStatsEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(2, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0).getClass().getName())
+        .isEqualTo("io.grpc.census.CensusTracingModule$TracingClientInterceptor");
+    assertThat(effectiveInterceptors.get(1)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableTracing() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setTracingEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(2, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0).getClass().getName())
+        .isEqualTo("io.grpc.census.CensusStatsModule$StatsClientInterceptor");
+    assertThat(effectiveInterceptors.get(1)).isSameInstanceAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableBoth() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setStatsEnabled(false);
+    builder.setTracingEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertThat(effectiveInterceptors).containsExactly(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void idleTimeout() {
+    assertEquals(ManagedChannelImplBuilder.IDLE_MODE_DEFAULT_TIMEOUT_MILLIS,
+        builder.idleTimeoutMillis);
+
+    builder.idleTimeout(Long.MAX_VALUE, TimeUnit.DAYS);
+    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.idleTimeoutMillis);
+
+    builder.idleTimeout(ManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS,
+        TimeUnit.DAYS);
+    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.idleTimeoutMillis);
+
+    try {
+      builder.idleTimeout(0, TimeUnit.SECONDS);
+      fail("Should throw");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    builder.idleTimeout(1, TimeUnit.NANOSECONDS);
+    assertEquals(ManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
+        builder.idleTimeoutMillis);
+
+    builder.idleTimeout(30, TimeUnit.SECONDS);
+    assertEquals(TimeUnit.SECONDS.toMillis(30), builder.idleTimeoutMillis);
+  }
+
+  @Test
+  public void maxRetryAttempts() {
+    assertEquals(5, builder.maxRetryAttempts);
+
+    builder.maxRetryAttempts(3);
+    assertEquals(3, builder.maxRetryAttempts);
+  }
+
+  @Test
+  public void maxHedgedAttempts() {
+    assertEquals(5, builder.maxHedgedAttempts);
+
+    builder.maxHedgedAttempts(3);
+    assertEquals(3, builder.maxHedgedAttempts);
+  }
+
+  @Test
+  public void retryBufferSize() {
+    assertEquals(1L << 24, builder.retryBufferSize);
+
+    builder.retryBufferSize(3456L);
+    assertEquals(3456L, builder.retryBufferSize);
+  }
+
+  @Test
+  public void perRpcBufferLimit() {
+    assertEquals(1L << 20, builder.perRpcBufferLimit);
+
+    builder.perRpcBufferLimit(3456L);
+    assertEquals(3456L, builder.perRpcBufferLimit);
+  }
+
+  @Test
+  public void retryBufferSizeInvalidArg() {
+    thrown.expect(IllegalArgumentException.class);
+    builder.retryBufferSize(0L);
+  }
+
+  @Test
+  public void perRpcBufferLimitInvalidArg() {
+    thrown.expect(IllegalArgumentException.class);
+    builder.perRpcBufferLimit(0L);
+  }
+
+  @Test
+  public void disableRetry() {
+    builder.enableRetry();
+    assertTrue(builder.retryEnabled);
+
+    builder.disableRetry();
+    assertFalse(builder.retryEnabled);
+
+    builder.enableRetry();
+    assertTrue(builder.retryEnabled);
+
+    builder.disableRetry();
+    assertFalse(builder.retryEnabled);
+  }
+
+  @Test
+  public void defaultServiceConfig_nullKey() {
+    Map<String, Object> config = new HashMap<>();
+    config.put(null, "val");
+
+    thrown.expect(IllegalArgumentException.class);
+    builder.defaultServiceConfig(config);
+  }
+
+  @Test
+  public void defaultServiceConfig_intKey() {
+    Map<Integer, Object> subConfig = new HashMap<>();
+    subConfig.put(3, "val");
+    Map<String, Object> config = new HashMap<>();
+    config.put("key", subConfig);
+
+    thrown.expect(IllegalArgumentException.class);
+    builder.defaultServiceConfig(config);
+  }
+
+  @Test
+  public void defaultServiceConfig_intValue() {
+    Map<String, Object> config = new HashMap<>();
+    config.put("key", 3);
+
+    thrown.expect(IllegalArgumentException.class);
+    builder.defaultServiceConfig(config);
+  }
+
+  @Test
+  public void defaultServiceConfig_nested() {
+    Map<String, Object> config = new HashMap<>();
+    List<Object> list1 = new ArrayList<>();
+    list1.add(123D);
+    list1.add(null);
+    list1.add(true);
+    list1.add("str");
+    Map<String, Object> map2 = new HashMap<>();
+    map2.put("key2", false);
+    map2.put("key3", null);
+    map2.put("key4", Collections.singletonList("v4"));
+    map2.put("key4", 3.14D);
+    map2.put("key5", new HashMap<String, Object>());
+    list1.add(map2);
+    config.put("key1", list1);
+
+    builder.defaultServiceConfig(config);
+
+    assertThat(builder.defaultServiceConfig).containsExactlyEntriesIn(config);
+  }
+
+  @Test
+  public void disableNameResolverServiceConfig() {
+    assertThat(builder.lookUpServiceConfig).isTrue();
+
+    builder.disableServiceConfigLookUp();
+    assertThat(builder.lookUpServiceConfig).isFalse();
+  }
 }
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
index c551d26..a2a2925 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
@@ -62,7 +62,7 @@
 import io.grpc.Status;
 import io.grpc.StringMarshaller;
 import io.grpc.internal.FakeClock.ScheduledTask;
-import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
+import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder;
 import io.grpc.internal.TestUtils.MockClientTransportInfo;
 import java.net.SocketAddress;
 import java.net.URI;
@@ -161,12 +161,7 @@
         .thenReturn(timer.getScheduledExecutorService());
 
     ManagedChannelImplBuilder builder = new ManagedChannelImplBuilder("fake://target",
-        new ClientTransportFactoryBuilder() {
-          @Override public ClientTransportFactory buildClientTransportFactory() {
-            throw new UnsupportedOperationException();
-          }
-        },
-        null);
+        new UnsupportedClientTransportFactoryBuilder(), null);
 
     builder
         .nameResolverFactory(mockNameResolverFactory)
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
index 5a7933e..97208ae 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
@@ -109,6 +109,7 @@
 import io.grpc.internal.ManagedChannelImpl.ScParser;
 import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
 import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider;
+import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder;
 import io.grpc.internal.ServiceConfigUtil.PolicySelection;
 import io.grpc.internal.TestUtils.MockClientTransportInfo;
 import io.grpc.stub.ClientCalls;
@@ -328,13 +329,8 @@
         .thenReturn(balancerRpcExecutor.getScheduledExecutorService());
 
     channelBuilder = new ManagedChannelImplBuilder(TARGET,
-        new ClientTransportFactoryBuilder() {
-          @Override
-          public ClientTransportFactory buildClientTransportFactory() {
-            throw new UnsupportedOperationException();
-          }
-        },
-        new FixedPortProvider(DEFAULT_PORT));
+        new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT));
+
     channelBuilder
         .nameResolverFactory(new FakeNameResolverFactory.Builder(expectedUri).build())
         .defaultLoadBalancingPolicy(MOCK_POLICY_NAME)
diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java
index 49f094a..16c6f3b 100644
--- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java
+++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java
@@ -46,8 +46,8 @@
 import io.grpc.NameResolver;
 import io.grpc.NameResolver.ConfigOrError;
 import io.grpc.Status;
-import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
 import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider;
+import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder;
 import java.net.SocketAddress;
 import java.net.URI;
 import java.util.ArrayList;
@@ -200,13 +200,7 @@
     when(executorPool.getObject()).thenReturn(executor.getScheduledExecutorService());
 
     channelBuilder = new ManagedChannelImplBuilder(TARGET,
-        new ClientTransportFactoryBuilder() {
-          @Override
-          public ClientTransportFactory buildClientTransportFactory() {
-            throw new UnsupportedOperationException();
-          }
-        },
-        new FixedPortProvider(DEFAULT_PORT));
+        new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT));
 
     channelBuilder
         .nameResolverFactory(new FakeNameResolverFactory.Builder(expectedUri).build())