Skip to content

Commit 919115b

Browse files
committed
simplify logic and add documentation
Signed-off-by: alperozturk <alper_ozturk@proton.me>
1 parent d1c1436 commit 919115b

File tree

2 files changed

+108
-62
lines changed

2 files changed

+108
-62
lines changed

app/src/main/java/com/nextcloud/client/network/ConnectivityService.java

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
/*
22
* Nextcloud - Android Client
33
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
45
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
56
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
67
*/
78
package com.nextcloud.client.network;
89

910

11+
import android.net.ConnectivityManager;
12+
import android.net.Network;
13+
14+
import com.nextcloud.client.account.Server;
15+
import com.nextcloud.client.account.UserAccountManager;
16+
1017
import androidx.annotation.NonNull;
1118

1219
/**
@@ -15,31 +22,81 @@
1522
*/
1623
public interface ConnectivityService {
1724
/**
18-
* Checks the availability of the server and the device's internet connection.
19-
* <p>
20-
* This method performs a network request to verify if the server is accessible and
21-
* checks if the device has an active internet connection.
22-
* </p>
25+
* Asynchronously checks whether both the device's local network connection
26+
* and the Nextcloud server are available.
27+
*
28+
* <p>This method executes its logic on a background thread and posts the result
29+
* back to the main thread through the provided {@link GenericCallback}.</p>
30+
*
31+
* <p>The check is based on {@link #isInternetWalled()} — if the Internet is not
32+
* walled (i.e., the server is reachable and not restricted by a captive portal),
33+
* this method reports {@code true}. Otherwise, it reports {@code false}.</p>
2334
*
24-
* @param callback A callback to handle the result of the network and server availability check.
35+
* @param callback a callback that receives {@code true} when the network and
36+
* Nextcloud server are reachable, or {@code false} otherwise.
2537
*/
2638
void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback);
2739

40+
/**
41+
* Checks whether the device currently has an active, validated Internet connection
42+
* via a recognized transport type.
43+
*
44+
* <p>This method queries the Android {@link ConnectivityManager} to determine
45+
* whether there is an active {@link Network} with Internet capability and an
46+
* acceptable transport such as Wi-Fi, Cellular, Ethernet, VPN, or Bluetooth.</p>
47+
*
48+
* <p>For Android 12 (API 31) and newer, USB network transport is also considered valid.</p>
49+
*
50+
* <p>Note: This only confirms that the Android system has validated Internet access,
51+
* not necessarily that the Nextcloud server itself is reachable.</p>
52+
*
53+
* @return {@code true} if the device is connected to the Internet through a supported
54+
* transport type; {@code false} otherwise.
55+
*/
2856
boolean isConnected();
2957

3058
/**
31-
* Check if server is accessible by issuing HTTP status check request.
32-
* Since this call involves network traffic, it should not be called
33-
* on a main thread.
59+
* Determines whether the device's current Internet connection is "walled" — that is,
60+
* restricted by a captive portal or other form of network access control that prevents
61+
* full connectivity to the Nextcloud server.
62+
*
63+
* <p>This method does <strong>not</strong> test general Internet reachability (e.g. Google or DNS),
64+
* but rather focuses on the ability to access the configured Nextcloud server directly.
65+
* In other words, it checks whether the server can be reached without network interference
66+
* such as a hotel's captive portal, Wi-Fi login page, or similar restrictions.</p>
67+
*
68+
* <p>The implementation performs the following steps:</p>
69+
* <ul>
70+
* <li>Uses cached results from {@link WalledCheckCache} when available to avoid
71+
* redundant network calls.</li>
72+
* <li>Retrieves the active {@link Server} from {@link UserAccountManager}.</li>
73+
* <li>If connected via a non-metered Wi-Fi network, issues a lightweight
74+
* HTTP {@code GET} request to the server’s <code>/index.php/204</code> endpoint
75+
* (which should respond with HTTP 204 No Content when connectivity is healthy).</li>
76+
* <li>If the response differs from the expected 204 No Content, the connection is
77+
* assumed to be behind a captive portal or otherwise restricted.</li>
78+
* <li>If no active network or server is detected, the method assumes the Internet
79+
* is walled.</li>
80+
* </ul>
3481
*
35-
* @return True if server is unreachable, false otherwise
82+
* <p>Results are cached for subsequent checks to minimize unnecessary HTTP requests.</p>
83+
*
84+
* @return {@code true} if the Internet appears to be walled (e.g. captive portal or
85+
* restricted access); {@code false} if the Nextcloud server is reachable and
86+
* the network allows normal Internet access.
3687
*/
3788
boolean isInternetWalled();
3889

3990
/**
40-
* Get current network connectivity status.
91+
* Returns a {@link Connectivity} object that represents the current network state.
92+
*
93+
* <p>This includes whether the device is connected, whether the network is metered,
94+
* and whether it uses Wi-Fi or Ethernet transport. It uses
95+
* {@link #isConnected()} to verify active Internet capability</p>
96+
*
97+
* <p>If no active network is found, {@link Connectivity#DISCONNECTED} is returned.</p>
4198
*
42-
* @return Network connectivity status in platform-agnostic format
99+
* @return a {@link Connectivity} instance describing the current network connection.
43100
*/
44101
Connectivity getConnectivity();
45102

app/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.concurrent.Executors;
2929

3030
import androidx.annotation.NonNull;
31+
import androidx.core.net.ConnectivityManagerCompat;
3132
import kotlin.jvm.functions.Function1;
3233

3334
class ConnectivityServiceImpl implements ConnectivityService {
@@ -70,22 +71,6 @@ public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callba
7071
});
7172
}
7273

73-
/**
74-
* Checks whether the device is currently connected to a network
75-
* that has verified Internet access.
76-
*
77-
* <p>This method performs multiple levels of validation:
78-
* <ul>
79-
* <li>Ensures there is an active network connection.</li>
80-
* <li>Retrieves and checks network capabilities.</li>
81-
* <li>Verifies that the active network provides and has validated Internet access.</li>
82-
* <li>Confirms that the network uses a supported transport type
83-
* (Wi-Fi, Cellular, Ethernet, VPN, etc.).</li>
84-
* </ul>
85-
*
86-
* @return {@code true} if the device is connected to the Internet via a valid transport type;
87-
* {@code false} otherwise.
88-
*/
8974
@Override
9075
public boolean isConnected() {
9176
Network nw = platformConnectivityManager.getActiveNetwork();
@@ -98,10 +83,7 @@ public boolean isConnected() {
9883
return false;
9984
}
10085

101-
// Verify that the network both claims to provide Internet
102-
// and has been validated (i.e., Internet is actually reachable).
103-
if (actNw.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && actNw.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
104-
86+
if (actNw.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
10587
// Check if the active network uses one of the recognized transport types.
10688
if (actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
10789
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
@@ -127,37 +109,31 @@ public boolean isInternetWalled() {
127109
final Boolean cachedValue = walledCheckCache.getValue();
128110
if (cachedValue != null) {
129111
return cachedValue;
130-
}
131-
132-
final Server server = accountManager.getUser().getServer();
133-
final String baseServerAddress = server.getUri().toString();
134-
135-
if (!isConnected() || baseServerAddress.isEmpty()) {
136-
walledCheckCache.setValue(true);
137-
return true;
138-
}
139-
140-
final GetMethod get = requestBuilder.invoke(baseServerAddress + CONNECTIVITY_CHECK_ROUTE);
141-
try {
142-
final PlainClient client = clientFactory.createPlainClient();
143-
int status = get.execute(client);
144-
145-
boolean isWalled = !(status == HttpStatus.SC_NO_CONTENT && get.getResponseContentLength() <= 0);
146-
147-
if (isWalled) {
148-
Log_OC.w(TAG, "isInternetWalled(): Failed to GET " + CONNECTIVITY_CHECK_ROUTE +
149-
", assuming connectivity is impaired");
112+
} else {
113+
Server server = accountManager.getUser().getServer();
114+
String baseServerAddress = server.getUri().toString();
115+
116+
boolean result;
117+
Connectivity c = getConnectivity();
118+
if (c != null && c.isConnected() && c.isWifi() && !c.isMetered() && !baseServerAddress.isEmpty()) {
119+
GetMethod get = requestBuilder.invoke(baseServerAddress + CONNECTIVITY_CHECK_ROUTE);
120+
PlainClient client = clientFactory.createPlainClient();
121+
122+
int status = get.execute(client);
123+
124+
// Content-Length is not available when using chunked transfer encoding, so check for -1 as well
125+
result = !(status == HttpStatus.SC_NO_CONTENT && get.getResponseContentLength() <= 0);
126+
get.releaseConnection();
127+
if (result) {
128+
Log_OC.w(TAG, "isInternetWalled(): Failed to GET " + CONNECTIVITY_CHECK_ROUTE + "," +
129+
" assuming connectivity is impaired");
130+
}
131+
} else {
132+
result = (c != null && !c.isConnected());
150133
}
151134

152-
// Cache and return result
153-
walledCheckCache.setValue(isWalled);
154-
return isWalled;
155-
} catch (Exception e) {
156-
Log_OC.e(TAG, "Exception while checking internet walled state", e);
157-
walledCheckCache.setValue(true);
158-
return true;
159-
} finally {
160-
get.releaseConnection();
135+
walledCheckCache.setValue(result);
136+
return result;
161137
}
162138
}
163139

@@ -170,12 +146,25 @@ public Connectivity getConnectivity() {
170146

171147
NetworkCapabilities nc = platformConnectivityManager.getNetworkCapabilities(nw);
172148
boolean isConnected = isConnected();
173-
boolean isMetered = (nc != null) && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
149+
boolean isMetered = isNetworkMetered(nc);
174150
boolean isWifi = (nc != null) &&
175151
(nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
176152
nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||
177153
nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE));
178154

179155
return new Connectivity(isConnected, isMetered, isWifi, null);
180156
}
157+
158+
private boolean isNetworkMetered(NetworkCapabilities nc) {
159+
try {
160+
if (nc != null) {
161+
return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
162+
} else {
163+
return ConnectivityManagerCompat.isActiveNetworkMetered(platformConnectivityManager);
164+
}
165+
} catch (RuntimeException e) {
166+
Log_OC.e(TAG, "Exception when checking network capabilities", e);
167+
return false;
168+
}
169+
}
181170
}

0 commit comments

Comments
 (0)