Skip to content

Commit

Permalink
[Android] Add NetworkChange implementation using PeriodicTimer (#80548)
Browse files Browse the repository at this point in the history
* Add NetworkChange implementation using PeriodicTimer

* Code cleanup

* Rename functions

* Update csproj
  • Loading branch information
simonrozsival authored Jan 19, 2023
1 parent 8598658 commit b6f12dd
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@
<Compile Include="System\Net\NetworkInformation\AndroidIPInterfaceProperties.cs" />
<Compile Include="System\Net\NetworkInformation\AndroidIPv4InterfaceProperties.cs" />
<Compile Include="System\Net\NetworkInformation\AndroidIPv6InterfaceProperties.cs" />
<Compile Include="System\Net\NetworkInformation\NetworkInterfacePal.Android.cs" />
<Compile Include="System\Net\NetworkInformation\AndroidNetworkInterface.cs" />
<Compile Include="System\Net\NetworkInformation\NetworkInterfacePal.Android.cs" />
<Compile Include="System\Net\NetworkInformation\NetworkAddressChange.Android.cs" />
</ItemGroup>
<!-- OSX -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'osx' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos' or '$(TargetPlatformIdentifier)' == 'freebsd'">
Expand Down Expand Up @@ -188,7 +189,7 @@
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'freebsd'">
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Libraries.cs" Link="Common\Interop\FreeBSD\Interop.Libraries.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'linux' or '$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'freebsd'">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'linux' or '$(TargetPlatformIdentifier)' == 'freebsd'">
<Compile Include="System\Net\NetworkInformation\NetworkAddressChange.Unix.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.NetworkChange.cs" Link="Common\Interop\Unix\System.Native\Interop.NetworkChange.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.NetworkInformation
{
public partial class NetworkChange
{
private static readonly TimeSpan s_timerInterval = TimeSpan.FromSeconds(2);
private static readonly object s_lockObj = new();

private static Task? s_loopTask;
private static CancellationTokenSource? s_cancellationTokenSource;
private static IPAddress[]? s_lastIpAddresses;

[UnsupportedOSPlatform("illumos")]
[UnsupportedOSPlatform("solaris")]
public static event NetworkAddressChangedEventHandler? NetworkAddressChanged
{
add
{
if (value != null)
{
lock (s_lockObj)
{
if (s_addressChangedSubscribers.Count == 0 &&
s_availabilityChangedSubscribers.Count == 0)
{
CreateAndStartLoop();
}
else
{
Debug.Assert(s_loopTask is not null);
}

s_addressChangedSubscribers.TryAdd(value, ExecutionContext.Capture());
}
}
}
remove
{
if (value != null)
{
lock (s_lockObj)
{
bool hadAddressChangedSubscribers = s_addressChangedSubscribers.Count != 0;
s_addressChangedSubscribers.Remove(value);

if (hadAddressChangedSubscribers && s_addressChangedSubscribers.Count == 0 &&
s_availabilityChangedSubscribers.Count == 0)
{
StopLoop();
}
}
}
}
}

[UnsupportedOSPlatform("illumos")]
[UnsupportedOSPlatform("solaris")]
public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityChanged
{
add
{
if (value != null)
{
lock (s_lockObj)
{
if (s_addressChangedSubscribers.Count == 0 &&
s_availabilityChangedSubscribers.Count == 0)
{
CreateAndStartLoop();
}
else
{
Debug.Assert(s_loopTask is not null);
}

s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture());
}
}
}
remove
{
if (value != null)
{
lock (s_lockObj)
{
bool hadSubscribers = s_addressChangedSubscribers.Count != 0 ||
s_availabilityChangedSubscribers.Count != 0;
s_availabilityChangedSubscribers.Remove(value);

if (hadSubscribers && s_addressChangedSubscribers.Count == 0 &&
s_availabilityChangedSubscribers.Count == 0)
{
StopLoop();
}
}
}
}
}

private static void CreateAndStartLoop()
{
Debug.Assert(s_cancellationTokenSource is null);
Debug.Assert(s_loopTask is null);

s_cancellationTokenSource = new CancellationTokenSource();
s_loopTask = Task.Run(() => PeriodicallyCheckIfNetworkChanged(s_cancellationTokenSource.Token));
}

private static void StopLoop()
{
Debug.Assert(s_cancellationTokenSource is not null);
Debug.Assert(s_loopTask is not null);

s_cancellationTokenSource.Cancel();

s_loopTask = null;
s_cancellationTokenSource = null;
s_lastIpAddresses = null;
}

private static async Task PeriodicallyCheckIfNetworkChanged(CancellationToken cancellationToken)
{
using var timer = new PeriodicTimer(s_timerInterval);

try
{
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false)
&& !cancellationToken.IsCancellationRequested)
{
CheckIfNetworkChanged();
}
}
catch (OperationCanceledException)
{
}
}

private static void CheckIfNetworkChanged()
{
var newAddresses = GetIPAddresses();
if (s_lastIpAddresses is IPAddress[] oldAddresses && NetworkChanged(oldAddresses, newAddresses))
{
OnNetworkChanged();
}

s_lastIpAddresses = newAddresses;
}

private static IPAddress[] GetIPAddresses()
{
var addresses = new List<IPAddress>();

var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var networkInterface in networkInterfaces)
{
var properties = networkInterface.GetIPProperties();
foreach (var addressInformation in properties.UnicastAddresses)
{
addresses.Add(addressInformation.Address);
}
}

return addresses.ToArray();
}

private static bool NetworkChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses)
{
if (oldAddresses.Length != newAddresses.Length)
{
return true;
}

for (int i = 0; i < newAddresses.Length; i++)
{
if (Array.IndexOf(oldAddresses, newAddresses[i]) == -1)
{
return true;
}
}

return false;
}

private static void OnNetworkChanged()
{
Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>? addressChangedSubscribers = null;
Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>? availabilityChangedSubscribers = null;

lock (s_lockObj)
{
if (s_addressChangedSubscribers.Count > 0)
{
addressChangedSubscribers = new Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>(s_addressChangedSubscribers);
}
if (s_availabilityChangedSubscribers.Count > 0)
{
availabilityChangedSubscribers = new Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>(s_availabilityChangedSubscribers);
}
}

if (addressChangedSubscribers != null)
{
foreach (KeyValuePair<NetworkAddressChangedEventHandler, ExecutionContext?>
subscriber in addressChangedSubscribers)
{
NetworkAddressChangedEventHandler handler = subscriber.Key;
ExecutionContext? ec = subscriber.Value;

if (ec == null) // Flow suppressed
{
handler(null, EventArgs.Empty);
}
else
{
ExecutionContext.Run(ec, s_runAddressChangedHandler, handler);
}
}
}

if (availabilityChangedSubscribers != null)
{
bool isAvailable = NetworkInterface.GetIsNetworkAvailable();
NetworkAvailabilityEventArgs args = isAvailable ? s_availableEventArgs : s_notAvailableEventArgs;
ContextCallback callbackContext = isAvailable ? s_runHandlerAvailable : s_runHandlerNotAvailable;
foreach (KeyValuePair<NetworkAvailabilityChangedEventHandler, ExecutionContext?>
subscriber in availabilityChangedSubscribers)
{
NetworkAvailabilityChangedEventHandler handler = subscriber.Key;
ExecutionContext? ec = subscriber.Value;

if (ec == null) // Flow suppressed
{
handler(null, args);
}
else
{
ExecutionContext.Run(ec, callbackContext, handler);
}
}
}
}
}
}

0 comments on commit b6f12dd

Please sign in to comment.