mirror of
https://github.com/nextcloud/android.git
synced 2024-11-25 06:35:48 +03:00
Merge pull request #9139 from nextcloud/suppress-walled-network-test-on-metered-wifi
Suppress walled network check when on metered wifi
This commit is contained in:
commit
2693c103b3
4 changed files with 103 additions and 28 deletions
|
@ -24,6 +24,7 @@ package com.nextcloud.client.network
|
||||||
import android.accounts.AccountManager
|
import android.accounts.AccountManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.os.Build
|
||||||
import com.nextcloud.client.account.UserAccountManagerImpl
|
import com.nextcloud.client.account.UserAccountManagerImpl
|
||||||
import com.nextcloud.client.network.ConnectivityServiceImpl.GetRequestBuilder
|
import com.nextcloud.client.network.ConnectivityServiceImpl.GetRequestBuilder
|
||||||
import com.owncloud.android.AbstractOnServerIT
|
import com.owncloud.android.AbstractOnServerIT
|
||||||
|
@ -44,7 +45,8 @@ class ConnectivityServiceImplIT : AbstractOnServerIT() {
|
||||||
connectivityManager,
|
connectivityManager,
|
||||||
userAccountManager,
|
userAccountManager,
|
||||||
clientFactory,
|
clientFactory,
|
||||||
requestBuilder
|
requestBuilder,
|
||||||
|
Build.VERSION.SDK_INT
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(sut.connectivity.isConnected)
|
assertTrue(sut.connectivity.isConnected)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Nextcloud Android client application
|
* Nextcloud Android client application
|
||||||
*
|
*
|
||||||
* @author Chris Narkiewicz
|
* @author Chris Narkiewicz
|
||||||
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -20,7 +20,9 @@
|
||||||
|
|
||||||
package com.nextcloud.client.network;
|
package com.nextcloud.client.network;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.Network;
|
||||||
import android.net.NetworkCapabilities;
|
import android.net.NetworkCapabilities;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ import com.nextcloud.operations.GetMethod;
|
||||||
import org.apache.commons.httpclient.HttpStatus;
|
import org.apache.commons.httpclient.HttpStatus;
|
||||||
|
|
||||||
import androidx.core.net.ConnectivityManagerCompat;
|
import androidx.core.net.ConnectivityManagerCompat;
|
||||||
|
import kotlin.jvm.functions.Function0;
|
||||||
import kotlin.jvm.functions.Function1;
|
import kotlin.jvm.functions.Function1;
|
||||||
|
|
||||||
class ConnectivityServiceImpl implements ConnectivityService {
|
class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
|
@ -40,6 +43,7 @@ class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
private final UserAccountManager accountManager;
|
private final UserAccountManager accountManager;
|
||||||
private final ClientFactory clientFactory;
|
private final ClientFactory clientFactory;
|
||||||
private final GetRequestBuilder requestBuilder;
|
private final GetRequestBuilder requestBuilder;
|
||||||
|
private final int sdkVersion;
|
||||||
|
|
||||||
static class GetRequestBuilder implements Function1<String, GetMethod> {
|
static class GetRequestBuilder implements Function1<String, GetMethod> {
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,17 +55,19 @@ class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
ConnectivityServiceImpl(ConnectivityManager platformConnectivityManager,
|
ConnectivityServiceImpl(ConnectivityManager platformConnectivityManager,
|
||||||
UserAccountManager accountManager,
|
UserAccountManager accountManager,
|
||||||
ClientFactory clientFactory,
|
ClientFactory clientFactory,
|
||||||
GetRequestBuilder requestBuilder) {
|
GetRequestBuilder requestBuilder,
|
||||||
|
int sdkVersion) {
|
||||||
this.platformConnectivityManager = platformConnectivityManager;
|
this.platformConnectivityManager = platformConnectivityManager;
|
||||||
this.accountManager = accountManager;
|
this.accountManager = accountManager;
|
||||||
this.clientFactory = clientFactory;
|
this.clientFactory = clientFactory;
|
||||||
this.requestBuilder = requestBuilder;
|
this.requestBuilder = requestBuilder;
|
||||||
|
this.sdkVersion = sdkVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternetWalled() {
|
public boolean isInternetWalled() {
|
||||||
Connectivity c = getConnectivity();
|
Connectivity c = getConnectivity();
|
||||||
if (c.isConnected() && c.isWifi()) {
|
if (c.isConnected() && c.isWifi() && !c.isMetered()) {
|
||||||
|
|
||||||
Server server = accountManager.getUser().getServer();
|
Server server = accountManager.getUser().getServer();
|
||||||
String baseServerAddress = server.getUri().toString();
|
String baseServerAddress = server.getUri().toString();
|
||||||
|
@ -76,7 +82,6 @@ class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
|
|
||||||
// Content-Length is not available when using chunked transfer encoding, so check for -1 as well
|
// Content-Length is not available when using chunked transfer encoding, so check for -1 as well
|
||||||
boolean result = !(status == HttpStatus.SC_NO_CONTENT && get.getResponseContentLength() <= 0);
|
boolean result = !(status == HttpStatus.SC_NO_CONTENT && get.getResponseContentLength() <= 0);
|
||||||
|
|
||||||
get.releaseConnection();
|
get.releaseConnection();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -96,21 +101,9 @@ class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
|
|
||||||
if (networkInfo != null) {
|
if (networkInfo != null) {
|
||||||
boolean isConnected = networkInfo.isConnectedOrConnecting();
|
boolean isConnected = networkInfo.isConnectedOrConnecting();
|
||||||
|
|
||||||
// more detailed check
|
// more detailed check
|
||||||
boolean isMetered;
|
boolean isMetered;
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
isMetered = isNetworkMetered();
|
||||||
NetworkCapabilities networkCapabilities = platformConnectivityManager.getNetworkCapabilities(
|
|
||||||
platformConnectivityManager.getActiveNetwork());
|
|
||||||
|
|
||||||
if (networkCapabilities != null) {
|
|
||||||
isMetered = !networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
|
||||||
} else {
|
|
||||||
isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
|
|
||||||
}
|
|
||||||
boolean isWifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI || hasNonCellularConnectivity();
|
boolean isWifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI || hasNonCellularConnectivity();
|
||||||
return new Connectivity(isConnected, isMetered, isWifi, null);
|
return new Connectivity(isConnected, isMetered, isWifi, null);
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,6 +111,21 @@ class ConnectivityServiceImpl implements ConnectivityService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi") // false positive due to mocking
|
||||||
|
private boolean isNetworkMetered() {
|
||||||
|
if (sdkVersion >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
final Network network = platformConnectivityManager.getActiveNetwork();
|
||||||
|
NetworkCapabilities networkCapabilities = platformConnectivityManager.getNetworkCapabilities(network);
|
||||||
|
if (networkCapabilities != null) {
|
||||||
|
return !networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||||
|
} else {
|
||||||
|
return ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasNonCellularConnectivity() {
|
private boolean hasNonCellularConnectivity() {
|
||||||
for (NetworkInfo networkInfo : platformConnectivityManager.getAllNetworkInfo()) {
|
for (NetworkInfo networkInfo : platformConnectivityManager.getAllNetworkInfo()) {
|
||||||
if (networkInfo.isConnectedOrConnecting() && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI ||
|
if (networkInfo.isConnectedOrConnecting() && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI ||
|
||||||
|
|
|
@ -22,6 +22,7 @@ package com.nextcloud.client.network;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.nextcloud.client.account.UserAccountManager;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
|
|
||||||
|
@ -40,7 +41,8 @@ public class NetworkModule {
|
||||||
return new ConnectivityServiceImpl(connectivityManager,
|
return new ConnectivityServiceImpl(connectivityManager,
|
||||||
accountManager,
|
accountManager,
|
||||||
clientFactory,
|
clientFactory,
|
||||||
new ConnectivityServiceImpl.GetRequestBuilder()
|
new ConnectivityServiceImpl.GetRequestBuilder(),
|
||||||
|
Build.VERSION.SDK_INT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Nextcloud Android client application
|
* Nextcloud Android client application
|
||||||
*
|
*
|
||||||
* @author Chris Narkiewicz
|
* @author Chris Narkiewicz
|
||||||
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -20,7 +20,10 @@
|
||||||
package com.nextcloud.client.network
|
package com.nextcloud.client.network
|
||||||
|
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkInfo
|
import android.net.NetworkInfo
|
||||||
|
import android.os.Build
|
||||||
import com.nextcloud.client.account.Server
|
import com.nextcloud.client.account.Server
|
||||||
import com.nextcloud.client.account.User
|
import com.nextcloud.client.account.User
|
||||||
import com.nextcloud.client.account.UserAccountManager
|
import com.nextcloud.client.account.UserAccountManager
|
||||||
|
@ -28,8 +31,10 @@ import com.nextcloud.client.logger.Logger
|
||||||
import com.nextcloud.common.PlainClient
|
import com.nextcloud.common.PlainClient
|
||||||
import com.nextcloud.operations.GetMethod
|
import com.nextcloud.operations.GetMethod
|
||||||
import com.nhaarman.mockitokotlin2.any
|
import com.nhaarman.mockitokotlin2.any
|
||||||
|
import com.nhaarman.mockitokotlin2.eq
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.never
|
import com.nhaarman.mockitokotlin2.never
|
||||||
|
import com.nhaarman.mockitokotlin2.times
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion
|
import com.owncloud.android.lib.resources.status.OwnCloudVersion
|
||||||
|
@ -89,6 +94,12 @@ class ConnectivityServiceTest {
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var requestBuilder: ConnectivityServiceImpl.GetRequestBuilder
|
lateinit var requestBuilder: ConnectivityServiceImpl.GetRequestBuilder
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var network: Network
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var networkCapabilities: NetworkCapabilities
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var logger: Logger
|
lateinit var logger: Logger
|
||||||
|
|
||||||
|
@ -108,11 +119,16 @@ class ConnectivityServiceTest {
|
||||||
platformConnectivityManager,
|
platformConnectivityManager,
|
||||||
accountManager,
|
accountManager,
|
||||||
clientFactory,
|
clientFactory,
|
||||||
requestBuilder
|
requestBuilder,
|
||||||
|
Build.VERSION_CODES.M
|
||||||
)
|
)
|
||||||
|
|
||||||
|
whenever(networkCapabilities.hasCapability(eq(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)))
|
||||||
|
.thenReturn(true)
|
||||||
|
whenever(platformConnectivityManager.activeNetwork).thenReturn(network)
|
||||||
whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
|
whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
|
||||||
whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
|
whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
|
||||||
|
whenever(platformConnectivityManager.getNetworkCapabilities(any())).thenReturn(networkCapabilities)
|
||||||
whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
|
whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
|
||||||
whenever(clientFactory.createPlainClient()).thenReturn(client)
|
whenever(clientFactory.createPlainClient()).thenReturn(client)
|
||||||
whenever(user.server).thenReturn(newServer)
|
whenever(user.server).thenReturn(newServer)
|
||||||
|
@ -235,12 +251,55 @@ class ConnectivityServiceTest {
|
||||||
whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
|
whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
|
||||||
whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
|
whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
|
||||||
whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_20)
|
whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_20)
|
||||||
assertTrue(
|
connectivityService.connectivity.let {
|
||||||
"Precondition failed",
|
assertTrue(it.isConnected)
|
||||||
connectivityService.connectivity.let {
|
assertTrue(it.isWifi)
|
||||||
it.isConnected && it.isWifi
|
assertFalse(it.isMetered)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request not sent when not connected`() {
|
||||||
|
// GIVEN
|
||||||
|
// network is not connected
|
||||||
|
whenever(networkInfo.isConnectedOrConnecting).thenReturn(false)
|
||||||
|
whenever(networkInfo.isConnected).thenReturn(false)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
// connectivity is checked
|
||||||
|
val result = connectivityService.isInternetWalled
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
// connection is walled
|
||||||
|
// request is not sent
|
||||||
|
assertTrue("Server should not be accessible", result)
|
||||||
|
verify(requestBuilder, never()).invoke(any())
|
||||||
|
verify(client, never()).execute(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request not sent when wifi is metered`() {
|
||||||
|
// GIVEN
|
||||||
|
// network is connected to wifi
|
||||||
|
// wifi is metered
|
||||||
|
whenever(networkCapabilities.hasCapability(any())).thenReturn(false) // this test is mocked for API M
|
||||||
|
whenever(platformConnectivityManager.isActiveNetworkMetered).thenReturn(true)
|
||||||
|
connectivityService.connectivity.let {
|
||||||
|
assertTrue("should be connected", it.isConnected)
|
||||||
|
assertTrue("should be connected to wifi", it.isWifi)
|
||||||
|
assertTrue("check mocking, this check is complicated and depends on SDK version", it.isMetered)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
// connectivity is checked
|
||||||
|
val result = connectivityService.isInternetWalled
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
// assume internet is not walled
|
||||||
|
// request is not sent
|
||||||
|
assertFalse("Server should not be accessible", result)
|
||||||
|
verify(requestBuilder, never()).invoke(any())
|
||||||
|
verify(getRequest, never()).execute(any<PlainClient>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -260,7 +319,7 @@ class ConnectivityServiceTest {
|
||||||
// request is not sent
|
// request is not sent
|
||||||
assertTrue("Server should not be accessible", result)
|
assertTrue("Server should not be accessible", result)
|
||||||
verify(requestBuilder, never()).invoke(any())
|
verify(requestBuilder, never()).invoke(any())
|
||||||
verify(client, never()).execute(any())
|
verify(getRequest, never()).execute(any<PlainClient>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mockResponse(contentLength: Long = 0, status: Int = HttpStatus.SC_OK) {
|
fun mockResponse(contentLength: Long = 0, status: Int = HttpStatus.SC_OK) {
|
||||||
|
@ -274,18 +333,21 @@ class ConnectivityServiceTest {
|
||||||
fun `status 204 means internet is not walled`() {
|
fun `status 204 means internet is not walled`() {
|
||||||
mockResponse(contentLength = 0, status = HttpStatus.SC_NO_CONTENT)
|
mockResponse(contentLength = 0, status = HttpStatus.SC_NO_CONTENT)
|
||||||
assertFalse(connectivityService.isInternetWalled)
|
assertFalse(connectivityService.isInternetWalled)
|
||||||
|
verify(getRequest, times(1)).execute(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `status 204 and no content length means internet is not walled`() {
|
fun `status 204 and no content length means internet is not walled`() {
|
||||||
mockResponse(contentLength = -1, status = HttpStatus.SC_NO_CONTENT)
|
mockResponse(contentLength = -1, status = HttpStatus.SC_NO_CONTENT)
|
||||||
assertFalse(connectivityService.isInternetWalled)
|
assertFalse(connectivityService.isInternetWalled)
|
||||||
|
verify(getRequest, times(1)).execute(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `other status than 204 means internet is walled`() {
|
fun `other status than 204 means internet is walled`() {
|
||||||
mockResponse(contentLength = 0, status = HttpStatus.SC_GONE)
|
mockResponse(contentLength = 0, status = HttpStatus.SC_GONE)
|
||||||
assertTrue(connectivityService.isInternetWalled)
|
assertTrue(connectivityService.isInternetWalled)
|
||||||
|
verify(getRequest, times(1)).execute(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -295,6 +357,7 @@ class ConnectivityServiceTest {
|
||||||
val urlCaptor = ArgumentCaptor.forClass(String::class.java)
|
val urlCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||||
verify(requestBuilder).invoke(urlCaptor.capture())
|
verify(requestBuilder).invoke(urlCaptor.capture())
|
||||||
assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/index.php/204"))
|
assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/index.php/204"))
|
||||||
|
verify(getRequest, times(1)).execute(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue