Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package com.nextcloud.client.network

import android.accounts.AccountManager
import android.content.Context
import android.net.ConnectivityManager
import com.nextcloud.client.account.UserAccountManagerImpl
import com.nextcloud.client.core.ClockImpl
import com.nextcloud.client.network.ConnectivityServiceImpl.GetRequestBuilder
Expand All @@ -21,15 +20,14 @@ import org.junit.Test
class ConnectivityServiceImplIT : AbstractOnServerIT() {
@Test
fun testInternetWalled() {
val connectivityManager = targetContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val accountManager = targetContext.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val userAccountManager = UserAccountManagerImpl(targetContext, accountManager)
val clientFactory = ClientFactoryImpl(targetContext)
val requestBuilder = GetRequestBuilder()
val walledCheckCache = WalledCheckCache(ClockImpl())

val sut = ConnectivityServiceImpl(
connectivityManager,
targetContext,
userAccountManager,
clientFactory,
requestBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ package com.nextcloud.test

import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.network.NetworkChangeListener

/** A mocked connectivity service returning that the device is offline **/
class ConnectivityServiceOfflineMock : ConnectivityService {
override fun addListener(listener: NetworkChangeListener) = Unit
override fun removeListener(listener: NetworkChangeListener) = Unit
override fun isNetworkAndServerAvailable(callback: ConnectivityService.GenericCallback<Boolean>) {
callback.onComplete(false)
}

override fun isConnected(): Boolean = false

override fun isInternetWalled(): Boolean = false

override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI
}
11 changes: 11 additions & 0 deletions app/src/androidTest/java/com/owncloud/android/AbstractIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.network.NetworkChangeListener;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.nextcloud.client.preferences.DarkMode;
import com.nextcloud.common.NextcloudClient;
Expand Down Expand Up @@ -371,6 +372,16 @@ public void uploadFile(File file, String remotePath) {

public void uploadOCUpload(OCUpload ocUpload) {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public void addListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void removeListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.network.NetworkChangeListener;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
Expand Down Expand Up @@ -203,6 +204,16 @@ public void uploadOCUpload(OCUpload ocUpload) {

public void uploadOCUpload(OCUpload ocUpload, int localBehaviour) {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public void addListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void removeListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback) {

Expand Down
35 changes: 33 additions & 2 deletions app/src/androidTest/java/com/owncloud/android/UploadIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.network.NetworkChangeListener;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
Expand Down Expand Up @@ -56,6 +57,16 @@ public class UploadIT extends AbstractOnServerIT {
targetContext.getContentResolver());

private ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public void addListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void removeListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback) {

Expand Down Expand Up @@ -268,6 +279,16 @@ public BatteryStatus getBattery() {
@Test
public void testUploadOnWifiOnlyButNoWifi() {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public void addListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void removeListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback) {

Expand All @@ -285,7 +306,7 @@ public boolean isInternetWalled() {

@Override
public Connectivity getConnectivity() {
return new Connectivity(true, false, false, true);
return new Connectivity(true, false, false, true, false);
}
};
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
Expand Down Expand Up @@ -357,6 +378,16 @@ public void testUploadOnWifiOnlyAndWifi() {
@Test
public void testUploadOnWifiOnlyButMeteredWifi() {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public void addListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void removeListener(@NonNull NetworkChangeListener listener) {

}

@Override
public void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback) {

Expand All @@ -374,7 +405,7 @@ public boolean isInternetWalled() {

@Override
public Connectivity getConnectivity() {
return new Connectivity(true, true, true, true);
return new Connectivity(true, true, true, true, false);
}
};
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.network.NetworkChangeListener
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
Expand All @@ -34,10 +35,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
private var uploadsStorageManager: UploadsStorageManager? = null

private val connectivityServiceMock: ConnectivityService = object : ConnectivityService {
override fun addListener(listener: NetworkChangeListener) = Unit
override fun removeListener(listener: NetworkChangeListener) = Unit
override fun isNetworkAndServerAvailable(callback: ConnectivityService.GenericCallback<Boolean>) = Unit

override fun isConnected(): Boolean = false

override fun isInternetWalled(): Boolean = false
override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI
}
Expand Down
6 changes: 3 additions & 3 deletions app/src/debug/java/com/nextcloud/test/TestActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.network.NetworkChangeListener
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.R
import com.owncloud.android.databinding.TestLayoutBinding
Expand Down Expand Up @@ -43,12 +44,11 @@ class TestActivity :
private lateinit var binding: TestLayoutBinding

val connectivityServiceMock: ConnectivityService = object : ConnectivityService {
override fun addListener(listener: NetworkChangeListener) = Unit
override fun removeListener(listener: NetworkChangeListener) = Unit
override fun isNetworkAndServerAvailable(callback: ConnectivityService.GenericCallback<Boolean>) = Unit

override fun isConnected(): Boolean = false

override fun isInternetWalled(): Boolean = false

override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import com.nextcloud.client.widget.DashboardWidgetConfigurationActivity;
import com.nextcloud.client.widget.DashboardWidgetProvider;
import com.nextcloud.client.widget.DashboardWidgetService;
import com.nextcloud.receiver.NetworkChangeReceiver;
import com.nextcloud.ui.ChooseAccountDialogFragment;
import com.nextcloud.ui.ChooseStorageLocationDialogFragment;
import com.nextcloud.ui.ImageDetailFragment;
Expand Down Expand Up @@ -324,9 +323,6 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract BootupBroadcastReceiver bootupBroadcastReceiver();

@ContributesAndroidInjector
abstract NetworkChangeReceiver networkChangeReceiver();

@ContributesAndroidInjector
abstract NotificationWork.NotificationReceiver notificationWorkBroadcastReceiver();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ data class Connectivity(
val isConnected: Boolean = false,
val isMetered: Boolean = false,
val isWifi: Boolean = false,
val isServerAvailable: Boolean? = null
val isServerAvailable: Boolean? = null,
val isVPN: Boolean = false
) {
companion object {
@JvmField
Expand All @@ -21,7 +22,8 @@ data class Connectivity(
isConnected = true,
isMetered = false,
isWifi = true,
isServerAvailable = true
isServerAvailable = true,
isVPN = false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,80 @@
package com.nextcloud.client.network;


import android.net.ConnectivityManager;
import android.net.Network;
import androidx.annotation.NonNull;

/**
* This service provides information about current network connectivity
* and server reachability.
*/
public interface ConnectivityService {
void addListener(@NonNull NetworkChangeListener listener);
void removeListener(@NonNull NetworkChangeListener listener);

/**
* Checks the availability of the server and the device's internet connection.
* <p>
* This method performs a network request to verify if the server is accessible and
* checks if the device has an active internet connection.
* </p>
* Asynchronously checks whether both the device's network connection
* and the Nextcloud server are available.
*
* <p>This method executes its logic on a background thread and posts the result
* back to the main thread through the provided {@link GenericCallback}.</p>
*
* @param callback A callback to handle the result of the network and server availability check.
* <p>The check is based on {@link #isInternetWalled()} — if the Internet is not
* walled (i.e., the server is reachable and not restricted by a captive portal),
* this method reports {@code true}. Otherwise, it reports {@code false}.</p>
*
* @param callback a callback that receives {@code true} when the network and
* Nextcloud server are reachable, or {@code false} otherwise.
*/
void isNetworkAndServerAvailable(@NonNull GenericCallback<Boolean> callback);

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

/**
* Check if server is accessible by issuing HTTP status check request.
* Since this call involves network traffic, it should not be called
* on a main thread.
* Determines whether the device's current Internet connection is "walled" — that is,
* restricted by a captive portal or other form of network access control that prevents
* full connectivity to the Nextcloud server.
*
* <p>This method does <strong>not</strong> test general Internet reachability (e.g. Google or DNS),
* but rather focuses on the ability to access the configured Nextcloud server directly.
* In other words, it checks whether the server can be reached without network interference
* such as a hotel's captive portal, Wi-Fi login page, or similar restrictions.</p>
*
* @return True if server is unreachable, false otherwise
* <p>Results are cached for subsequent checks to minimize unnecessary HTTP requests.</p>
*
* @return {@code true} if the Internet appears to be walled (e.g. captive portal or
* restricted access); {@code false} if the Nextcloud server is reachable and
* the network allows normal Internet access.
*/
boolean isInternetWalled();

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

Expand Down
Loading
Loading