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