From 69a93b79608146b516131af2da6811004d5a6dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Rozs=C3=ADval?= Date: Wed, 8 Feb 2023 02:14:01 +0100 Subject: [PATCH] [Android] Add NetworkChange implementation using PeriodicTimer (#80548) (#81349) * Add NetworkChange implementation using PeriodicTimer * Code cleanup * Rename functions * Update csproj --- .../src/System.Net.NetworkInformation.csproj | 5 +- .../NetworkAddressChange.Android.cs | 247 ++++++++++++++++++ 2 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs diff --git a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index b1c5a10f45a64..7520e586642f2 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -152,8 +152,9 @@ - + + @@ -187,7 +188,7 @@ - + diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs new file mode 100644 index 0000000000000..5f7833826408d --- /dev/null +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs @@ -0,0 +1,247 @@ +// 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; + + 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(); + } + } + } + } + } + + 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(); + + 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? addressChangedSubscribers = null; + Dictionary? availabilityChangedSubscribers = null; + + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count > 0) + { + addressChangedSubscribers = new Dictionary(s_addressChangedSubscribers); + } + if (s_availabilityChangedSubscribers.Count > 0) + { + availabilityChangedSubscribers = new Dictionary(s_availabilityChangedSubscribers); + } + } + + if (addressChangedSubscribers != null) + { + foreach (KeyValuePair + 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 + 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); + } + } + } + } + } +}