Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ dependencies {
androidTestImplementation(libs.core.testing)
// endregion

testImplementation(libs.kotlinx.coroutines.test)

// region other libraries
compileOnly(libs.org.jbundle.util.osgi.wrapped.org.apache.http.client)
implementation(libs.commons.httpclient.commons.httpclient) // remove after entire switch to lib v2
Expand Down
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 @@ -648,7 +648,6 @@ class FileUploadHelper {
files: List<OCFile>,
accountName: String
): Pair<List<SyncedFolderEntity>, List<OCFile>> {

val autoUploadFolders = mutableListOf<SyncedFolderEntity>()
val nonAutoUploadFiles = mutableListOf<OCFile>()

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
)
}
}
19 changes: 19 additions & 0 deletions app/src/main/java/com/nextcloud/client/network/ConnectivityKey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.client.network

import com.nextcloud.client.account.UserAccountManager

data class ConnectivityKey(val accountName: String, val baseUrl: String) {
companion object {
fun getBy(accountManager: UserAccountManager): ConnectivityKey = ConnectivityKey(
accountManager.user.accountName,
accountManager.user.server.uri.toString()
)
}
}
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