library.network.maui/Network/Platforms/iOS/PlatformConnectivity.cs

196 lines
6.1 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Net;
using CoreFoundation;
using CoreTelephony;
using SystemConfiguration;
namespace Blahblah.Library.Network;
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
partial class Connectivity
{
static readonly Lazy<CTCellularData> cellularData = new(() => new CTCellularData());
static ReachabilityListener? listener;
private static partial NetworkStatus PlatformGetStatus()
{
var restricted = cellularData.Value.RestrictedState == CTCellularDataRestrictedState.Restricted;
var internetStatus = InternetConnectionStatus();
if (internetStatus == NetworkStatus.ReachableViaCarrierDataNetwork && !restricted)
{
return NetworkStatus.ReachableViaCarrierDataNetwork;
}
if (internetStatus == NetworkStatus.ReachableViaWiFiNetwork)
{
return NetworkStatus.ReachableViaWiFiNetwork;
}
var remoteStatus = RemoteHostStatus();
if (remoteStatus == NetworkStatus.ReachableViaCarrierDataNetwork && !restricted)
{
return NetworkStatus.ReachableViaCarrierDataNetwork;
}
if (remoteStatus == NetworkStatus.ReachableViaWiFiNetwork)
{
return NetworkStatus.ReachableViaWiFiNetwork;
}
return NetworkStatus.NotReachable;
}
static NetworkStatus RemoteHostStatus()
{
using var remote = new NetworkReachability(hostName);
if (!remote.TryGetFlags(out var flags))
{
return NetworkStatus.NotReachable;
}
if (!IsReachableWithoutRequiringConnection(flags))
{
return NetworkStatus.NotReachable;
}
if ((flags & NetworkReachabilityFlags.IsWWAN) != 0)
{
return NetworkStatus.ReachableViaCarrierDataNetwork;
}
return NetworkStatus.ReachableViaWiFiNetwork;
}
static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags)
{
// Is it reachable with the current network configuration?
var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0;
// Do we need a connection to reach it?
var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
// Since the network stack will automatically try to get the WAN up,
// probe that
if ((flags & NetworkReachabilityFlags.IsWWAN) != 0)
{
noConnectionRequired = true;
}
return isReachable && noConnectionRequired;
}
static NetworkStatus InternetConnectionStatus()
{
var status = NetworkStatus.NotReachable;
var defaultNetworkAvailable = IsNetworkAvailable(out var flags);
// If the connection is reachable and no connection is required, then assume it's WiFi
if (defaultNetworkAvailable)
{
status = NetworkStatus.ReachableViaWiFiNetwork;
}
else if ((flags & NetworkReachabilityFlags.IsWWAN) != 0)
{
// If it's a WWAN connection..
status = NetworkStatus.ReachableViaCarrierDataNetwork;
}
// If the connection is on-demand or on-traffic and no user intervention
// is required, then assume WiFi.
if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) &&
(flags & NetworkReachabilityFlags.InterventionRequired) == 0)
{
status = NetworkStatus.ReachableViaWiFiNetwork;
}
return status;
}
private static bool IsNetworkAvailable(out NetworkReachabilityFlags flags)
{
var ip = new IPAddress(0);
using var route = new NetworkReachability(ip);
if (!route.TryGetFlags(out flags))
{
return false;
}
return IsReachableWithoutRequiringConnection(flags);
}
private static partial void StartListeners()
{
StopListeners();
listener = new ReachabilityListener();
listener.ReachabilityChanged += OnConnectivityChanged;
}
private static partial void StopListeners()
{
if (listener == null)
{
return;
}
listener.ReachabilityChanged -= OnConnectivityChanged;
listener.Dispose();
listener = null;
}
class ReachabilityListener : IDisposable
{
NetworkReachability? routeReachability;
NetworkReachability? remoteReachability;
public event Action? ReachabilityChanged;
public ReachabilityListener()
{
var ip = new IPAddress(0);
routeReachability = new NetworkReachability(ip);
routeReachability.SetNotification(OnChange);
routeReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault);
remoteReachability = new NetworkReachability(hostName);
// Need to probe before we queue, or we wont get any meaningful values
// this only happens when you create NetworkReachability from a hostname
remoteReachability.TryGetFlags(out var flags);
remoteReachability.SetNotification(OnChange);
remoteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault);
cellularData.Value.RestrictionDidUpdateNotifier = new(OnRestrictedStateChanged);
}
public void Dispose()
{
routeReachability?.Dispose();
routeReachability = null;
remoteReachability?.Dispose();
remoteReachability = null;
cellularData.Value.RestrictionDidUpdateNotifier = null;
}
void OnRestrictedStateChanged(CTCellularDataRestrictedState state)
{
ReachabilityChanged?.Invoke();
}
async void OnChange(NetworkReachabilityFlags flags)
{
// Add in artifical delay so the connection status has time to change
// else it will return true no matter what.
await Task.Delay(100);
ReachabilityChanged?.Invoke();
}
}
}