mirror of https://github.com/grpc/grpc-java.git
android: add AndroidChannelBuilder (#4172)
This commit is contained in:
parent
a47266ea4a
commit
ba86a86c77
|
|
@ -63,6 +63,7 @@ them before continuing, and set them again when resuming.
|
||||||
$ MAJOR=1 MINOR=7 PATCH=0 # Set appropriately for new release
|
$ MAJOR=1 MINOR=7 PATCH=0 # Set appropriately for new release
|
||||||
$ VERSION_FILES=(
|
$ VERSION_FILES=(
|
||||||
build.gradle
|
build.gradle
|
||||||
|
android/build.gradle
|
||||||
android-interop-testing/app/build.gradle
|
android-interop-testing/app/build.gradle
|
||||||
core/src/main/java/io/grpc/internal/GrpcUtil.java
|
core/src/main/java/io/grpc/internal/GrpcUtil.java
|
||||||
cronet/build.gradle
|
cronet/build.gradle
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
description = 'gRPC: Android'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url "https://plugins.gradle.org/m2/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||||
|
classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "net.ltgt.errorprone"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 14
|
||||||
|
targetSdkVersion 27
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
includeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'io.grpc:grpc-core:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
|
testImplementation 'io.grpc:grpc-okhttp:1.12.0-SNAPSHOT' // CURRENT_GRPC_VERSION
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
testImplementation 'org.robolectric:robolectric:3.7.1'
|
||||||
|
testImplementation 'com.google.truth:truth:0.39'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="io.grpc.android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.android;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.ClientCall;
|
||||||
|
import io.grpc.ConnectivityState;
|
||||||
|
import io.grpc.ExperimentalApi;
|
||||||
|
import io.grpc.ForwardingChannelBuilder;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.internal.GrpcUtil;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link ManagedChannel} that, when provided with a {@link Context}, will automatically
|
||||||
|
* monitor the Android device's network state to smoothly handle intermittent network failures.
|
||||||
|
*
|
||||||
|
* <p>Requires the Android ACCESS_NETWORK_STATE permission.
|
||||||
|
*
|
||||||
|
* @since 1.12.0
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
|
||||||
|
public final class AndroidChannelBuilder extends ForwardingChannelBuilder<AndroidChannelBuilder> {
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "AndroidChannelBuilder";
|
||||||
|
|
||||||
|
@Nullable private static final Class<?> OKHTTP_CHANNEL_BUILDER_CLASS = findOkHttp();
|
||||||
|
|
||||||
|
private static final Class<?> findOkHttp() {
|
||||||
|
try {
|
||||||
|
return Class.forName("io.grpc.okhttp.OkHttpChannelBuilder");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ManagedChannelBuilder delegateBuilder;
|
||||||
|
|
||||||
|
@Nullable private Context context;
|
||||||
|
|
||||||
|
public static final AndroidChannelBuilder forTarget(String target) {
|
||||||
|
return new AndroidChannelBuilder(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AndroidChannelBuilder forAddress(String name, int port) {
|
||||||
|
return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AndroidChannelBuilder(String target) {
|
||||||
|
if (OKHTTP_CHANNEL_BUILDER_CLASS == null) {
|
||||||
|
throw new UnsupportedOperationException("No ManagedChannelBuilder found on the classpath");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
delegateBuilder =
|
||||||
|
(ManagedChannelBuilder)
|
||||||
|
OKHTTP_CHANNEL_BUILDER_CLASS
|
||||||
|
.getMethod("forTarget", String.class)
|
||||||
|
.invoke(null, target);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create ManagedChannelBuilder", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enables automatic monitoring of the device's network state. */
|
||||||
|
public AndroidChannelBuilder context(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ManagedChannelBuilder<?> delegate() {
|
||||||
|
return delegateBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ManagedChannel build() {
|
||||||
|
return new AndroidChannel(delegateBuilder.build(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an OkHttp channel and handles invoking the appropriate methods (e.g., {@link
|
||||||
|
* ManagedChannel#resetConnectBackoff}) when the device network state changes.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static final class AndroidChannel extends ManagedChannel {
|
||||||
|
|
||||||
|
private final ManagedChannel delegate;
|
||||||
|
|
||||||
|
@Nullable private final Context context;
|
||||||
|
@Nullable private final ConnectivityManager connectivityManager;
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private Runnable unregisterRunnable;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
AndroidChannel(final ManagedChannel delegate, @Nullable Context context) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
connectivityManager =
|
||||||
|
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
configureNetworkMonitoring();
|
||||||
|
} else {
|
||||||
|
connectivityManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private void configureNetworkMonitoring() {
|
||||||
|
// Eagerly check current network state to verify app has required permissions
|
||||||
|
NetworkInfo currentNetwork;
|
||||||
|
try {
|
||||||
|
currentNetwork = connectivityManager.getActiveNetworkInfo();
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.w(
|
||||||
|
LOG_TAG,
|
||||||
|
"Failed to configure network monitoring. Does app have ACCESS_NETWORK_STATE"
|
||||||
|
+ " permission?",
|
||||||
|
e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android N added the registerDefaultNetworkCallback API to listen to changes in the device's
|
||||||
|
// default network. For earlier Android API levels, use the BroadcastReceiver API.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) {
|
||||||
|
// The connection status may change before registration of the listener is complete, but
|
||||||
|
// this will at worst result in invoking resetConnectBackoff() instead of enterIdle() (or
|
||||||
|
// vice versa) on the first network change.
|
||||||
|
boolean isConnected = currentNetwork != null && currentNetwork.isConnected();
|
||||||
|
|
||||||
|
final DefaultNetworkCallback defaultNetworkCallback =
|
||||||
|
new DefaultNetworkCallback(isConnected);
|
||||||
|
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
|
||||||
|
unregisterRunnable =
|
||||||
|
new Runnable() {
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
final NetworkReceiver networkReceiver = new NetworkReceiver();
|
||||||
|
IntentFilter networkIntentFilter =
|
||||||
|
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||||
|
context.registerReceiver(networkReceiver, networkIntentFilter);
|
||||||
|
unregisterRunnable =
|
||||||
|
new Runnable() {
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
context.unregisterReceiver(networkReceiver);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterNetworkListener() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (unregisterRunnable != null) {
|
||||||
|
unregisterRunnable.run();
|
||||||
|
unregisterRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ManagedChannel shutdown() {
|
||||||
|
unregisterNetworkListener();
|
||||||
|
return delegate.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return delegate.isShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return delegate.isTerminated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ManagedChannel shutdownNow() {
|
||||||
|
unregisterNetworkListener();
|
||||||
|
return delegate.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return delegate.awaitTermination(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
|
||||||
|
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
|
||||||
|
return delegate.newCall(methodDescriptor, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authority() {
|
||||||
|
return delegate.authority();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectivityState getState(boolean requestConnection) {
|
||||||
|
return delegate.getState(requestConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
|
||||||
|
delegate.notifyWhenStateChanged(source, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetConnectBackoff() {
|
||||||
|
delegate.resetConnectBackoff();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enterIdle() {
|
||||||
|
delegate.enterIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Respond to changes in the default network. Only used on API levels 24+. */
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
|
||||||
|
private boolean isConnected = false;
|
||||||
|
|
||||||
|
private DefaultNetworkCallback(boolean isConnected) {
|
||||||
|
this.isConnected = isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAvailable(Network network) {
|
||||||
|
if (isConnected) {
|
||||||
|
delegate.enterIdle();
|
||||||
|
} else {
|
||||||
|
delegate.resetConnectBackoff();
|
||||||
|
}
|
||||||
|
isConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLost(Network network) {
|
||||||
|
isConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Respond to network changes. Only used on API levels < 24. */
|
||||||
|
private class NetworkReceiver extends BroadcastReceiver {
|
||||||
|
private boolean isConnected = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
ConnectivityManager conn =
|
||||||
|
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
|
||||||
|
boolean wasConnected = isConnected;
|
||||||
|
isConnected = networkInfo != null && networkInfo.isConnected();
|
||||||
|
if (isConnected && !wasConnected) {
|
||||||
|
delegate.resetConnectBackoff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,373 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018, gRPC Authors All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.android;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION_CODES.LOLLIPOP;
|
||||||
|
import static android.os.Build.VERSION_CODES.N;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.robolectric.RuntimeEnvironment.getApiLevel;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.ClientCall;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.annotation.Implementation;
|
||||||
|
import org.robolectric.annotation.Implements;
|
||||||
|
import org.robolectric.shadows.ShadowConnectivityManager;
|
||||||
|
import org.robolectric.shadows.ShadowNetwork;
|
||||||
|
import org.robolectric.shadows.ShadowNetworkInfo;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(shadows = {AndroidChannelBuilderTest.ShadowDefaultNetworkListenerConnectivityManager.class})
|
||||||
|
public final class AndroidChannelBuilderTest {
|
||||||
|
private static final NetworkInfo WIFI_CONNECTED =
|
||||||
|
ShadowNetworkInfo.newInstance(
|
||||||
|
NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, true);
|
||||||
|
private static final NetworkInfo WIFI_DISCONNECTED =
|
||||||
|
ShadowNetworkInfo.newInstance(
|
||||||
|
NetworkInfo.DetailedState.DISCONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, false);
|
||||||
|
private final NetworkInfo MOBILE_CONNECTED =
|
||||||
|
ShadowNetworkInfo.newInstance(
|
||||||
|
NetworkInfo.DetailedState.CONNECTED,
|
||||||
|
ConnectivityManager.TYPE_MOBILE,
|
||||||
|
ConnectivityManager.TYPE_MOBILE_MMS,
|
||||||
|
true,
|
||||||
|
true);
|
||||||
|
private final NetworkInfo MOBILE_DISCONNECTED =
|
||||||
|
ShadowNetworkInfo.newInstance(
|
||||||
|
NetworkInfo.DetailedState.DISCONNECTED,
|
||||||
|
ConnectivityManager.TYPE_MOBILE,
|
||||||
|
ConnectivityManager.TYPE_MOBILE_MMS,
|
||||||
|
true,
|
||||||
|
false);
|
||||||
|
|
||||||
|
private ConnectivityManager connectivityManager;
|
||||||
|
private ShadowConnectivityManager shadowConnectivityManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
connectivityManager =
|
||||||
|
(ConnectivityManager)
|
||||||
|
RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
shadowConnectivityManager = shadowOf(connectivityManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void channelBuilderClassFoundReflectively() {
|
||||||
|
// This should not throw with OkHttpChannelBuilder on the classpath
|
||||||
|
AndroidChannelBuilder.forTarget("target");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 23)
|
||||||
|
public void nullContextDoesNotThrow_api23() {
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
|
||||||
|
|
||||||
|
// Network change and shutdown should be no-op for the channel without an Android Context
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
androidChannel.shutdown();
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 24)
|
||||||
|
public void nullContextDoesNotThrow_api24() {
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
|
||||||
|
|
||||||
|
// Network change and shutdown should be no-op for the channel without an Android Context
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
androidChannel.shutdown();
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 23)
|
||||||
|
public void resetConnectBackoff_api23() {
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel =
|
||||||
|
new AndroidChannelBuilder.AndroidChannel(
|
||||||
|
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
|
||||||
|
// On API levels < 24, the broadcast receiver will invoke resetConnectBackoff() on the first
|
||||||
|
// connectivity action broadcast regardless of previous connection status
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// The broadcast receiver may fire when the active network status has not actually changed
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Drop the connection
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Notify that a new but not connected network is available
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Establish a connection
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||||
|
|
||||||
|
// Disconnect, then shutdown the channel and verify that the broadcast receiver has been
|
||||||
|
// unregistered
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
androidChannel.shutdown();
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||||
|
// enterIdle is not called on API levels < 24
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 24)
|
||||||
|
public void resetConnectBackoffAndEnterIdle_api24() {
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_DISCONNECTED);
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel =
|
||||||
|
new AndroidChannelBuilder.AndroidChannel(
|
||||||
|
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||||
|
|
||||||
|
// Establish an initial network connection
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
|
||||||
|
|
||||||
|
// Switch to another network to trigger enterIdle()
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Switch to an offline network and then to null
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_DISCONNECTED);
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(1);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Establish a connection
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||||
|
|
||||||
|
// Disconnect, then shutdown the channel and verify that the callback has been unregistered
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
androidChannel.shutdown();
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(2);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 24)
|
||||||
|
public void newChannelWithConnection_entersIdleOnConnectionChange_api24() {
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(MOBILE_CONNECTED);
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel =
|
||||||
|
new AndroidChannelBuilder.AndroidChannel(
|
||||||
|
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||||
|
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
|
||||||
|
|
||||||
|
androidChannel.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 23)
|
||||||
|
public void shutdownNowUnregistersBroadcastReceiver_api23() {
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel =
|
||||||
|
new AndroidChannelBuilder.AndroidChannel(
|
||||||
|
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||||
|
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
androidChannel.shutdownNow();
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
RuntimeEnvironment.application.sendBroadcast(
|
||||||
|
new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(sdk = 24)
|
||||||
|
public void shutdownNowUnregistersNetworkCallback_api24() {
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(null);
|
||||||
|
TestChannel delegateChannel = new TestChannel();
|
||||||
|
ManagedChannel androidChannel =
|
||||||
|
new AndroidChannelBuilder.AndroidChannel(
|
||||||
|
delegateChannel, RuntimeEnvironment.application.getApplicationContext());
|
||||||
|
|
||||||
|
androidChannel.shutdownNow();
|
||||||
|
shadowConnectivityManager.setActiveNetworkInfo(WIFI_CONNECTED);
|
||||||
|
|
||||||
|
assertThat(delegateChannel.resetCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends Robolectric ShadowConnectivityManager to handle Android N's
|
||||||
|
* registerDefaultNetworkCallback API.
|
||||||
|
*/
|
||||||
|
@Implements(value = ConnectivityManager.class)
|
||||||
|
public static class ShadowDefaultNetworkListenerConnectivityManager
|
||||||
|
extends ShadowConnectivityManager {
|
||||||
|
private HashSet<ConnectivityManager.NetworkCallback> defaultNetworkCallbacks = new HashSet<>();
|
||||||
|
|
||||||
|
public ShadowDefaultNetworkListenerConnectivityManager() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActiveNetworkInfo(NetworkInfo activeNetworkInfo) {
|
||||||
|
if (getApiLevel() >= N) {
|
||||||
|
NetworkInfo previousNetworkInfo = getActiveNetworkInfo();
|
||||||
|
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
|
||||||
|
notifyDefaultNetworkCallbacksOnAvailable(
|
||||||
|
ShadowNetwork.newInstance(activeNetworkInfo.getType() /* use type as network ID */));
|
||||||
|
} else if (previousNetworkInfo != null) {
|
||||||
|
notifyDefaultNetworkCallbacksOnLost(
|
||||||
|
ShadowNetwork.newInstance(
|
||||||
|
previousNetworkInfo.getType() /* use type as network ID */));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setActiveNetworkInfo(activeNetworkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDefaultNetworkCallbacksOnAvailable(Network network) {
|
||||||
|
for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
|
||||||
|
networkCallback.onAvailable(network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDefaultNetworkCallbacksOnLost(Network network) {
|
||||||
|
for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
|
||||||
|
networkCallback.onLost(network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation(minSdk = N)
|
||||||
|
protected void registerDefaultNetworkCallback(
|
||||||
|
ConnectivityManager.NetworkCallback networkCallback) {
|
||||||
|
defaultNetworkCallbacks.add(networkCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation(minSdk = LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback) {
|
||||||
|
if (getApiLevel() >= N) {
|
||||||
|
if (networkCallback != null || defaultNetworkCallbacks.contains(networkCallback)) {
|
||||||
|
defaultNetworkCallbacks.remove(networkCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.unregisterNetworkCallback(networkCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestChannel extends ManagedChannel {
|
||||||
|
int resetCount;
|
||||||
|
int enterIdleCount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ManagedChannel shutdown() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ManagedChannel shutdownNow() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
|
||||||
|
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authority() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetConnectBackoff() {
|
||||||
|
resetCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enterIdle() {
|
||||||
|
enterIdleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,19 @@ ln -s "/tmp/protobuf-${PROTOBUF_VERSION}/$(uname -s)-$(uname -p)" /tmp/protobuf
|
||||||
|
|
||||||
./gradlew install
|
./gradlew install
|
||||||
|
|
||||||
# Build Cronet
|
# Build grpc-cronet
|
||||||
|
|
||||||
pushd cronet
|
pushd cronet
|
||||||
./cronet_deps.sh
|
./cronet_deps.sh
|
||||||
../gradlew build
|
../gradlew build
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
# Build grpc-android
|
||||||
|
|
||||||
|
pushd android
|
||||||
|
../gradlew build
|
||||||
|
popd
|
||||||
|
|
||||||
# Build examples
|
# Build examples
|
||||||
|
|
||||||
cd ./examples/android/clientcache
|
cd ./examples/android/clientcache
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue