1 /* 2 * Copyright 2018 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.android; 18 19 import android.annotation.TargetApi; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.ConnectivityManager; 25 import android.net.Network; 26 import android.net.NetworkInfo; 27 import android.os.Build; 28 import android.util.Log; 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.base.Preconditions; 31 import io.grpc.CallOptions; 32 import io.grpc.ClientCall; 33 import io.grpc.ConnectivityState; 34 import io.grpc.ExperimentalApi; 35 import io.grpc.ForwardingChannelBuilder; 36 import io.grpc.ManagedChannel; 37 import io.grpc.ManagedChannelBuilder; 38 import io.grpc.MethodDescriptor; 39 import io.grpc.internal.GrpcUtil; 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.ScheduledExecutorService; 42 import java.util.concurrent.TimeUnit; 43 import javax.annotation.Nullable; 44 import javax.annotation.concurrent.GuardedBy; 45 import javax.net.ssl.SSLSocketFactory; 46 47 /** 48 * Builds a {@link ManagedChannel} that, when provided with a {@link Context}, will automatically 49 * monitor the Android device's network state to smoothly handle intermittent network failures. 50 * 51 * <p>Currently only compatible with gRPC's OkHttp transport, which must be available at runtime. 52 * 53 * <p>Requires the Android ACCESS_NETWORK_STATE permission. 54 * 55 * @since 1.12.0 56 */ 57 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056") 58 public final class AndroidChannelBuilder extends ForwardingChannelBuilder<AndroidChannelBuilder> { 59 60 private static final String LOG_TAG = "AndroidChannelBuilder"; 61 62 @Nullable private static final Class<?> OKHTTP_CHANNEL_BUILDER_CLASS = findOkHttp(); 63 findOkHttp()64 private static final Class<?> findOkHttp() { 65 try { 66 return Class.forName("io.grpc.okhttp.OkHttpChannelBuilder"); 67 } catch (ClassNotFoundException e) { 68 return null; 69 } 70 } 71 72 private final ManagedChannelBuilder<?> delegateBuilder; 73 74 @Nullable private Context context; 75 forTarget(String target)76 public static final AndroidChannelBuilder forTarget(String target) { 77 return new AndroidChannelBuilder(target); 78 } 79 forAddress(String name, int port)80 public static AndroidChannelBuilder forAddress(String name, int port) { 81 return forTarget(GrpcUtil.authorityFromHostAndPort(name, port)); 82 } 83 fromBuilder(ManagedChannelBuilder<?> builder)84 public static AndroidChannelBuilder fromBuilder(ManagedChannelBuilder<?> builder) { 85 return new AndroidChannelBuilder(builder); 86 } 87 AndroidChannelBuilder(String target)88 private AndroidChannelBuilder(String target) { 89 if (OKHTTP_CHANNEL_BUILDER_CLASS == null) { 90 throw new UnsupportedOperationException("No ManagedChannelBuilder found on the classpath"); 91 } 92 try { 93 delegateBuilder = 94 (ManagedChannelBuilder) 95 OKHTTP_CHANNEL_BUILDER_CLASS 96 .getMethod("forTarget", String.class) 97 .invoke(null, target); 98 } catch (Exception e) { 99 throw new RuntimeException("Failed to create ManagedChannelBuilder", e); 100 } 101 } 102 AndroidChannelBuilder(ManagedChannelBuilder<?> delegateBuilder)103 private AndroidChannelBuilder(ManagedChannelBuilder<?> delegateBuilder) { 104 this.delegateBuilder = Preconditions.checkNotNull(delegateBuilder, "delegateBuilder"); 105 } 106 107 /** Enables automatic monitoring of the device's network state. */ context(Context context)108 public AndroidChannelBuilder context(Context context) { 109 this.context = context; 110 return this; 111 } 112 113 /** 114 * Set the delegate channel builder's transportExecutor. 115 * 116 * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured 117 * ManagedChannelBuilder instead. 118 */ 119 @Deprecated transportExecutor(@ullable Executor transportExecutor)120 public AndroidChannelBuilder transportExecutor(@Nullable Executor transportExecutor) { 121 try { 122 OKHTTP_CHANNEL_BUILDER_CLASS 123 .getMethod("transportExecutor", Executor.class) 124 .invoke(delegateBuilder, transportExecutor); 125 return this; 126 } catch (Exception e) { 127 throw new RuntimeException("Failed to invoke transportExecutor on delegate builder", e); 128 } 129 } 130 131 /** 132 * Set the delegate channel builder's sslSocketFactory. 133 * 134 * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured 135 * ManagedChannelBuilder instead. 136 */ 137 @Deprecated sslSocketFactory(SSLSocketFactory factory)138 public AndroidChannelBuilder sslSocketFactory(SSLSocketFactory factory) { 139 try { 140 OKHTTP_CHANNEL_BUILDER_CLASS 141 .getMethod("sslSocketFactory", SSLSocketFactory.class) 142 .invoke(delegateBuilder, factory); 143 return this; 144 } catch (Exception e) { 145 throw new RuntimeException("Failed to invoke sslSocketFactory on delegate builder", e); 146 } 147 } 148 149 /** 150 * Set the delegate channel builder's scheduledExecutorService. 151 * 152 * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured 153 * ManagedChannelBuilder instead. 154 */ 155 @Deprecated scheduledExecutorService( ScheduledExecutorService scheduledExecutorService)156 public AndroidChannelBuilder scheduledExecutorService( 157 ScheduledExecutorService scheduledExecutorService) { 158 try { 159 OKHTTP_CHANNEL_BUILDER_CLASS 160 .getMethod("scheduledExecutorService", ScheduledExecutorService.class) 161 .invoke(delegateBuilder, scheduledExecutorService); 162 return this; 163 } catch (Exception e) { 164 throw new RuntimeException( 165 "Failed to invoke scheduledExecutorService on delegate builder", e); 166 } 167 } 168 169 @Override delegate()170 protected ManagedChannelBuilder<?> delegate() { 171 return delegateBuilder; 172 } 173 174 @Override build()175 public ManagedChannel build() { 176 return new AndroidChannel(delegateBuilder.build(), context); 177 } 178 179 /** 180 * Wraps an OkHttp channel and handles invoking the appropriate methods (e.g., {@link 181 * ManagedChannel#resetConnectBackoff}) when the device network state changes. 182 */ 183 @VisibleForTesting 184 static final class AndroidChannel extends ManagedChannel { 185 186 private final ManagedChannel delegate; 187 188 @Nullable private final Context context; 189 @Nullable private final ConnectivityManager connectivityManager; 190 191 private final Object lock = new Object(); 192 193 @GuardedBy("lock") 194 private Runnable unregisterRunnable; 195 196 @VisibleForTesting AndroidChannel(final ManagedChannel delegate, @Nullable Context context)197 AndroidChannel(final ManagedChannel delegate, @Nullable Context context) { 198 this.delegate = delegate; 199 this.context = context; 200 201 if (context != null) { 202 connectivityManager = 203 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 204 try { 205 configureNetworkMonitoring(); 206 } catch (SecurityException e) { 207 Log.w( 208 LOG_TAG, 209 "Failed to configure network monitoring. Does app have ACCESS_NETWORK_STATE" 210 + " permission?", 211 e); 212 } 213 } else { 214 connectivityManager = null; 215 } 216 } 217 218 @GuardedBy("lock") configureNetworkMonitoring()219 private void configureNetworkMonitoring() { 220 // Android N added the registerDefaultNetworkCallback API to listen to changes in the device's 221 // default network. For earlier Android API levels, use the BroadcastReceiver API. 222 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) { 223 final DefaultNetworkCallback defaultNetworkCallback = new DefaultNetworkCallback(); 224 connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback); 225 unregisterRunnable = 226 new Runnable() { 227 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 228 @Override 229 public void run() { 230 connectivityManager.unregisterNetworkCallback(defaultNetworkCallback); 231 } 232 }; 233 } else { 234 final NetworkReceiver networkReceiver = new NetworkReceiver(); 235 IntentFilter networkIntentFilter = 236 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 237 context.registerReceiver(networkReceiver, networkIntentFilter); 238 unregisterRunnable = 239 new Runnable() { 240 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 241 @Override 242 public void run() { 243 context.unregisterReceiver(networkReceiver); 244 } 245 }; 246 } 247 } 248 unregisterNetworkListener()249 private void unregisterNetworkListener() { 250 synchronized (lock) { 251 if (unregisterRunnable != null) { 252 unregisterRunnable.run(); 253 unregisterRunnable = null; 254 } 255 } 256 } 257 258 @Override shutdown()259 public ManagedChannel shutdown() { 260 unregisterNetworkListener(); 261 return delegate.shutdown(); 262 } 263 264 @Override isShutdown()265 public boolean isShutdown() { 266 return delegate.isShutdown(); 267 } 268 269 @Override isTerminated()270 public boolean isTerminated() { 271 return delegate.isTerminated(); 272 } 273 274 @Override shutdownNow()275 public ManagedChannel shutdownNow() { 276 unregisterNetworkListener(); 277 return delegate.shutdownNow(); 278 } 279 280 @Override awaitTermination(long timeout, TimeUnit unit)281 public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 282 return delegate.awaitTermination(timeout, unit); 283 } 284 285 @Override newCall( MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions)286 public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall( 287 MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) { 288 return delegate.newCall(methodDescriptor, callOptions); 289 } 290 291 @Override authority()292 public String authority() { 293 return delegate.authority(); 294 } 295 296 @Override getState(boolean requestConnection)297 public ConnectivityState getState(boolean requestConnection) { 298 return delegate.getState(requestConnection); 299 } 300 301 @Override notifyWhenStateChanged(ConnectivityState source, Runnable callback)302 public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) { 303 delegate.notifyWhenStateChanged(source, callback); 304 } 305 306 @Override resetConnectBackoff()307 public void resetConnectBackoff() { 308 delegate.resetConnectBackoff(); 309 } 310 311 @Override enterIdle()312 public void enterIdle() { 313 delegate.enterIdle(); 314 } 315 316 /** Respond to changes in the default network. Only used on API levels 24+. */ 317 @TargetApi(Build.VERSION_CODES.N) 318 private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback { 319 // Registering a listener may immediate invoke onAvailable/onLost: the API docs do not specify 320 // if the methods are always invoked once, then again on any change, or only on change. When 321 // onAvailable() is invoked immediately without an actual network change, it's preferable to 322 // (spuriously) resetConnectBackoff() rather than enterIdle(), as the former is a no-op if the 323 // channel has already moved to CONNECTING. 324 private boolean isConnected = false; 325 326 @Override onAvailable(Network network)327 public void onAvailable(Network network) { 328 if (isConnected) { 329 delegate.enterIdle(); 330 } else { 331 delegate.resetConnectBackoff(); 332 } 333 isConnected = true; 334 } 335 336 @Override onLost(Network network)337 public void onLost(Network network) { 338 isConnected = false; 339 } 340 } 341 342 /** Respond to network changes. Only used on API levels < 24. */ 343 private class NetworkReceiver extends BroadcastReceiver { 344 private boolean isConnected = false; 345 346 @Override onReceive(Context context, Intent intent)347 public void onReceive(Context context, Intent intent) { 348 ConnectivityManager conn = 349 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 350 NetworkInfo networkInfo = conn.getActiveNetworkInfo(); 351 boolean wasConnected = isConnected; 352 isConnected = networkInfo != null && networkInfo.isConnected(); 353 if (isConnected && !wasConnected) { 354 delegate.resetConnectBackoff(); 355 } 356 } 357 } 358 } 359 } 360