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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.NetworkCapabilities;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

Expand All @@ -14,28 +15,57 @@ public class Network {
* Interface for callbacks when network status changes.
*/
interface NetworkStatusChangeListener {
void onNetworkStatusChanged(boolean wasLostEvent);
void onNetworkStatusChanged(NetworkStatus status);
}

// Status is derived from the callback arguments. Querying ConnectivityManager from inside a
// NetworkCallback can return state that is already out of sync with the callback.
class ConnectivityCallback extends NetworkCallback {

@Nullable
private android.net.Network currentNetwork;

@Override
public void onAvailable(@NonNull android.net.Network network) {
super.onAvailable(network);
currentNetwork = network;
// From API 26 onCapabilitiesChanged always follows onAvailable for the default network.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notifyStatus(statusFromCapabilities(connectivityManager.getNetworkCapabilities(network)));
}
}

@Override
public void onLost(@NonNull android.net.Network network) {
super.onLost(network);
statusChangeListener.onNetworkStatusChanged(true);
// A late onLost for the previous default network must not override the new one.
if (currentNetwork != null && !currentNetwork.equals(network)) {
return;
}
currentNetwork = null;
notifyStatus(new NetworkStatus());
}

@Override
public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
statusChangeListener.onNetworkStatusChanged(false);
currentNetwork = network;
notifyStatus(statusFromCapabilities(networkCapabilities));
}

private void notifyStatus(NetworkStatus status) {
NetworkStatusChangeListener listener = statusChangeListener;
if (listener != null) {
listener.onNetworkStatusChanged(status);
}
}
}

@Nullable
private NetworkStatusChangeListener statusChangeListener;
private volatile NetworkStatusChangeListener statusChangeListener;

private ConnectivityCallback connectivityCallback;
private boolean monitoring = false;
private Context context;
private ConnectivityManager connectivityManager;
private BroadcastReceiver receiver;
Expand Down Expand Up @@ -77,16 +107,24 @@ public NetworkStatus getNetworkStatus() {
android.net.Network activeNetwork = this.connectivityManager.getActiveNetwork();
NetworkCapabilities capabilities = this.connectivityManager.getNetworkCapabilities(this.connectivityManager.getActiveNetwork());
if (activeNetwork != null && capabilities != null) {
networkStatus.connected =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR;
} else {
networkStatus.connectionType = NetworkStatus.ConnectionType.UNKNOWN;
}
networkStatus = statusFromCapabilities(capabilities);
}
}
return networkStatus;
}

static NetworkStatus statusFromCapabilities(@Nullable NetworkCapabilities capabilities) {
NetworkStatus networkStatus = new NetworkStatus();
if (capabilities != null) {
networkStatus.connected =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR;
} else {
networkStatus.connectionType = NetworkStatus.ConnectionType.UNKNOWN;
}
}
return networkStatus;
Expand All @@ -109,16 +147,24 @@ private NetworkStatus getAndParseNetworkInfo() {
}

/**
* Register a network callback.
* Register a network callback. Does nothing if it is already registered.
*/
public void startMonitoring() {
public synchronized void startMonitoring() {
if (monitoring) {
return;
}
connectivityManager.registerDefaultNetworkCallback(connectivityCallback);
monitoring = true;
}

/**
* Unregister the network callback.
* Unregister the network callback. Does nothing if it is not registered.
*/
public void stopMonitoring() {
public synchronized void stopMonitoring() {
if (!monitoring) {
return;
}
monitoring = false;
connectivityManager.unregisterNetworkCallback(connectivityCallback);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.capacitorjs.plugins.network;

import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
Expand All @@ -13,25 +13,18 @@ public class NetworkPlugin extends Plugin {

private Network implementation;
public static final String NETWORK_CHANGE_EVENT = "networkStatusChange";
private NetworkStatus prePauseNetworkStatus = null;

@Nullable
private NetworkStatus lastNotifiedStatus = null;

/**
* Monitor for network status changes and fire our event.
*/
@Override
public void load() {
implementation = new Network(getContext());
Network.NetworkStatusChangeListener listener = (wasLostEvent) -> {
if (wasLostEvent) {
JSObject jsObject = new JSObject();
jsObject.put("connected", false);
jsObject.put("connectionType", "none");
notifyListeners(NETWORK_CHANGE_EVENT, jsObject);
} else {
updateNetworkStatus();
}
};
implementation.setStatusChangeListener(listener);
implementation.setStatusChangeListener(this::notifyIfChanged);
implementation.startMonitoring();
}

/**
Expand All @@ -40,6 +33,7 @@ public void load() {
@Override
protected void handleOnDestroy() {
implementation.setStatusChangeListener(null);
implementation.stopMonitoring();
}

/**
Expand All @@ -52,37 +46,38 @@ public void getStatus(PluginCall call) {
}

/**
* Register the IntentReceiver on resume
* Register the network callback on resume
*/
@Override
protected void handleOnResume() {
implementation.startMonitoring();
// Registering delivers the current default network, but nothing is delivered when there is
// none, so a connection lost while paused has to be reported from here.
NetworkStatus afterPauseNetworkStatus = implementation.getNetworkStatus();
if (
prePauseNetworkStatus != null &&
!afterPauseNetworkStatus.connected &&
(prePauseNetworkStatus.connected || afterPauseNetworkStatus.connectionType != prePauseNetworkStatus.connectionType)
) {
Log.d(
"Capacitor/NetworkPlugin",
"Detected pre-pause and after-pause network status mismatch. Updating network status and notifying listeners."
);
this.updateNetworkStatus();
if (!afterPauseNetworkStatus.connected && hasNotified()) {
Log.d("Capacitor/NetworkPlugin", "Network status is not connected after resume. Notifying listeners if it changed.");
notifyIfChanged(afterPauseNetworkStatus);
}
this.prePauseNetworkStatus = null;
}

/**
* Unregister the IntentReceiver on pause to avoid leaking it
* Unregister the network callback on pause to avoid leaking it
*/
@Override
protected void handleOnPause() {
this.prePauseNetworkStatus = implementation.getNetworkStatus();
implementation.stopMonitoring();
}

private void updateNetworkStatus() {
notifyListeners(NETWORK_CHANGE_EVENT, parseNetworkStatus(implementation.getNetworkStatus()));
private synchronized boolean hasNotified() {
return lastNotifiedStatus != null;
}

private synchronized void notifyIfChanged(NetworkStatus status) {
if (status.equals(lastNotifiedStatus)) {
return;
}
lastNotifiedStatus = status;
notifyListeners(NETWORK_CHANGE_EVENT, parseNetworkStatus(status));
}

private JSObject parseNetworkStatus(NetworkStatus networkStatus) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.capacitorjs.plugins.network;

import java.util.Objects;

public class NetworkStatus {

public enum ConnectionType {
Expand All @@ -21,4 +23,21 @@ public String getConnectionType() {

public boolean connected = false;
public ConnectionType connectionType = ConnectionType.NONE;

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NetworkStatus)) {
return false;
}
NetworkStatus other = (NetworkStatus) o;
return connected == other.connected && connectionType == other.connectionType;
}

@Override
public int hashCode() {
return Objects.hash(connected, connectionType);
}
}
2 changes: 1 addition & 1 deletion network/ios/Sources/NetworkPlugin/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class Network {
}

func currentStatus() -> Network.Connection {
return reachability?.connection.equivalentEnum ?? Connection.unavailable
return reachability?.currentConnection.equivalentEnum ?? Connection.unavailable
}
}

Expand Down
11 changes: 8 additions & 3 deletions network/ios/Sources/NetworkPlugin/Reachability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ public class Reachability {
return connection
}

public var connection: Connection {
if flags == nil {
try? setReachabilityFlags()
public var currentConnection: Connection {
var freshFlags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachabilityRef, &freshFlags) {
reachabilitySerialQueue.sync { flags = freshFlags }
}
return connection
}

public var connection: Connection {
if flags == nil { try? setReachabilityFlags() }
switch flags?.connection {
case .unavailable?, nil: return .unavailable
case .cellular?: return allowsCellularConnection ? .cellular : .unavailable
Expand Down