Skip to content

Commit 69a93b7

Browse files
[Android] Add NetworkChange implementation using PeriodicTimer (#80548) (#81349)
* Add NetworkChange implementation using PeriodicTimer * Code cleanup * Rename functions * Update csproj
1 parent aea0710 commit 69a93b7

File tree

2 files changed

+250
-2
lines changed

2 files changed

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

0 commit comments

Comments
 (0)