Skip to content

Commit 2964613

Browse files
[Android] Add NetworkChange implementation using PeriodicTimer (#80548) (#81350)
* Add NetworkChange implementation using PeriodicTimer * Code cleanup * Rename functions * Update csproj
1 parent 9fb2f8f commit 2964613

File tree

2 files changed

+254
-2
lines changed

2 files changed

+254
-2
lines changed

src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@
153153
<Compile Include="System\Net\NetworkInformation\AndroidIPInterfaceProperties.cs" />
154154
<Compile Include="System\Net\NetworkInformation\AndroidIPv4InterfaceProperties.cs" />
155155
<Compile Include="System\Net\NetworkInformation\AndroidIPv6InterfaceProperties.cs" />
156-
<Compile Include="System\Net\NetworkInformation\NetworkInterfacePal.Android.cs" />
157156
<Compile Include="System\Net\NetworkInformation\AndroidNetworkInterface.cs" />
157+
<Compile Include="System\Net\NetworkInformation\NetworkInterfacePal.Android.cs" />
158+
<Compile Include="System\Net\NetworkInformation\NetworkAddressChange.Android.cs" />
158159
</ItemGroup>
159160
<!-- OSX -->
160161
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'OSX' or '$(TargetPlatformIdentifier)' == 'iOS' or '$(TargetPlatformIdentifier)' == 'tvOS' or '$(TargetPlatformIdentifier)' == 'FreeBSD'">
@@ -188,7 +189,7 @@
188189
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'FreeBSD'">
189190
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Libraries.cs" Link="Common\Interop\FreeBSD\Interop.Libraries.cs" />
190191
</ItemGroup>
191-
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Linux' or '$(TargetPlatformIdentifier)' == 'Android' or '$(TargetPlatformIdentifier)' == 'FreeBSD'">
192+
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Linux' or '$(TargetPlatformIdentifier)' == 'FreeBSD'">
192193
<Compile Include="System\Net\NetworkInformation\NetworkAddressChange.Unix.cs" />
193194
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.NetworkChange.cs" Link="Common\Interop\Unix\System.Native\Interop.NetworkChange.cs" />
194195
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Runtime.Versioning;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace System.Net.NetworkInformation
11+
{
12+
public partial class NetworkChange
13+
{
14+
private static readonly TimeSpan s_timerInterval = TimeSpan.FromSeconds(2);
15+
private static readonly object s_lockObj = new();
16+
17+
private static Task? s_loopTask;
18+
private static CancellationTokenSource? s_cancellationTokenSource;
19+
private static IPAddress[]? s_lastIpAddresses;
20+
21+
[UnsupportedOSPlatform("illumos")]
22+
[UnsupportedOSPlatform("solaris")]
23+
public static event NetworkAddressChangedEventHandler? NetworkAddressChanged
24+
{
25+
add
26+
{
27+
if (value != null)
28+
{
29+
lock (s_lockObj)
30+
{
31+
if (s_addressChangedSubscribers.Count == 0 &&
32+
s_availabilityChangedSubscribers.Count == 0)
33+
{
34+
CreateAndStartLoop();
35+
}
36+
else
37+
{
38+
Debug.Assert(s_loopTask is not null);
39+
}
40+
41+
s_addressChangedSubscribers.TryAdd(value, ExecutionContext.Capture());
42+
}
43+
}
44+
}
45+
remove
46+
{
47+
if (value != null)
48+
{
49+
lock (s_lockObj)
50+
{
51+
bool hadAddressChangedSubscribers = s_addressChangedSubscribers.Count != 0;
52+
s_addressChangedSubscribers.Remove(value);
53+
54+
if (hadAddressChangedSubscribers && s_addressChangedSubscribers.Count == 0 &&
55+
s_availabilityChangedSubscribers.Count == 0)
56+
{
57+
StopLoop();
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
[UnsupportedOSPlatform("illumos")]
65+
[UnsupportedOSPlatform("solaris")]
66+
public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityChanged
67+
{
68+
add
69+
{
70+
if (value != null)
71+
{
72+
lock (s_lockObj)
73+
{
74+
if (s_addressChangedSubscribers.Count == 0 &&
75+
s_availabilityChangedSubscribers.Count == 0)
76+
{
77+
CreateAndStartLoop();
78+
}
79+
else
80+
{
81+
Debug.Assert(s_loopTask is not null);
82+
}
83+
84+
s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture());
85+
}
86+
}
87+
}
88+
remove
89+
{
90+
if (value != null)
91+
{
92+
lock (s_lockObj)
93+
{
94+
bool hadSubscribers = s_addressChangedSubscribers.Count != 0 ||
95+
s_availabilityChangedSubscribers.Count != 0;
96+
s_availabilityChangedSubscribers.Remove(value);
97+
98+
if (hadSubscribers && s_addressChangedSubscribers.Count == 0 &&
99+
s_availabilityChangedSubscribers.Count == 0)
100+
{
101+
StopLoop();
102+
}
103+
}
104+
}
105+
}
106+
}
107+
108+
private static void CreateAndStartLoop()
109+
{
110+
Debug.Assert(s_cancellationTokenSource is null);
111+
Debug.Assert(s_loopTask is null);
112+
113+
s_cancellationTokenSource = new CancellationTokenSource();
114+
s_loopTask = Task.Run(() => PeriodicallyCheckIfNetworkChanged(s_cancellationTokenSource.Token));
115+
}
116+
117+
private static void StopLoop()
118+
{
119+
Debug.Assert(s_cancellationTokenSource is not null);
120+
Debug.Assert(s_loopTask is not null);
121+
122+
s_cancellationTokenSource.Cancel();
123+
124+
s_loopTask = null;
125+
s_cancellationTokenSource = null;
126+
s_lastIpAddresses = null;
127+
}
128+
129+
private static async Task PeriodicallyCheckIfNetworkChanged(CancellationToken cancellationToken)
130+
{
131+
using var timer = new PeriodicTimer(s_timerInterval);
132+
133+
try
134+
{
135+
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false)
136+
&& !cancellationToken.IsCancellationRequested)
137+
{
138+
CheckIfNetworkChanged();
139+
}
140+
}
141+
catch (OperationCanceledException)
142+
{
143+
}
144+
}
145+
146+
private static void CheckIfNetworkChanged()
147+
{
148+
var newAddresses = GetIPAddresses();
149+
if (s_lastIpAddresses is IPAddress[] oldAddresses && NetworkChanged(oldAddresses, newAddresses))
150+
{
151+
OnNetworkChanged();
152+
}
153+
154+
s_lastIpAddresses = newAddresses;
155+
}
156+
157+
private static IPAddress[] GetIPAddresses()
158+
{
159+
var addresses = new List<IPAddress>();
160+
161+
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
162+
foreach (var networkInterface in networkInterfaces)
163+
{
164+
var properties = networkInterface.GetIPProperties();
165+
foreach (var addressInformation in properties.UnicastAddresses)
166+
{
167+
addresses.Add(addressInformation.Address);
168+
}
169+
}
170+
171+
return addresses.ToArray();
172+
}
173+
174+
private static bool NetworkChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses)
175+
{
176+
if (oldAddresses.Length != newAddresses.Length)
177+
{
178+
return true;
179+
}
180+
181+
for (int i = 0; i < newAddresses.Length; i++)
182+
{
183+
if (Array.IndexOf(oldAddresses, newAddresses[i]) == -1)
184+
{
185+
return true;
186+
}
187+
}
188+
189+
return false;
190+
}
191+
192+
private static void OnNetworkChanged()
193+
{
194+
Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>? addressChangedSubscribers = null;
195+
Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>? availabilityChangedSubscribers = null;
196+
197+
lock (s_lockObj)
198+
{
199+
if (s_addressChangedSubscribers.Count > 0)
200+
{
201+
addressChangedSubscribers = new Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>(s_addressChangedSubscribers);
202+
}
203+
if (s_availabilityChangedSubscribers.Count > 0)
204+
{
205+
availabilityChangedSubscribers = new Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>(s_availabilityChangedSubscribers);
206+
}
207+
}
208+
209+
if (addressChangedSubscribers != null)
210+
{
211+
foreach (KeyValuePair<NetworkAddressChangedEventHandler, ExecutionContext?>
212+
subscriber in addressChangedSubscribers)
213+
{
214+
NetworkAddressChangedEventHandler handler = subscriber.Key;
215+
ExecutionContext? ec = subscriber.Value;
216+
217+
if (ec == null) // Flow suppressed
218+
{
219+
handler(null, EventArgs.Empty);
220+
}
221+
else
222+
{
223+
ExecutionContext.Run(ec, s_runAddressChangedHandler, handler);
224+
}
225+
}
226+
}
227+
228+
if (availabilityChangedSubscribers != null)
229+
{
230+
bool isAvailable = NetworkInterface.GetIsNetworkAvailable();
231+
NetworkAvailabilityEventArgs args = isAvailable ? s_availableEventArgs : s_notAvailableEventArgs;
232+
ContextCallback callbackContext = isAvailable ? s_runHandlerAvailable : s_runHandlerNotAvailable;
233+
foreach (KeyValuePair<NetworkAvailabilityChangedEventHandler, ExecutionContext?>
234+
subscriber in availabilityChangedSubscribers)
235+
{
236+
NetworkAvailabilityChangedEventHandler handler = subscriber.Key;
237+
ExecutionContext? ec = subscriber.Value;
238+
239+
if (ec == null) // Flow suppressed
240+
{
241+
handler(null, args);
242+
}
243+
else
244+
{
245+
ExecutionContext.Run(ec, callbackContext, handler);
246+
}
247+
}
248+
}
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)