From b6cffffe3e3b0ff27ca1ae001fa51fa32996add8 Mon Sep 17 00:00:00 2001 From: James Montemagno Date: Mon, 26 Mar 2018 11:51:50 -0700 Subject: [PATCH] GH-8: Add Connectivity API (#6) * Add base connectivity * Update naming and put in Caboodle folder * Add: NetworkAccess and Profiles instead of simple bools * update sample and test device runner * Implement Connection profiles and connection change events. * Additional cleanup for compile * Add tests and samples * Fix saving list to not be reference type. Ensures that when profiles change an event is triggered. * Android dont' show non-connected profiles * Refactor some code Local functions!!!!!!!! * Update exception names for compile * Add connectivity documentation * Add docs * Fix build * Delete unit test 1 from build * Fix tests! woops * Cleanup connectivity API checks and add remarks for exceptions on Android --- Caboodle.Tests/Connectivity_Tests.cs | 24 ++ Caboodle/Caboodle.csproj | 4 + Caboodle/Clipboard/Clipboard.android.cs | 9 +- Caboodle/Connectivity/Connectivity.android.cs | 226 +++++++++++++++++ Caboodle/Connectivity/Connectivity.ios.cs | 61 +++++ .../Connectivity.ios.reachability.cs | 234 ++++++++++++++++++ .../Connectivity/Connectivity.netstandard.cs | 21 ++ Caboodle/Connectivity/Connectivity.shared.cs | 79 ++++++ .../Connectivity/Connectivity.shared.enums.cs | 25 ++ Caboodle/Connectivity/Connectivity.uwp.cs | 79 ++++++ Caboodle/Platform/Platform.android.cs | 11 + .../Properties/AndroidManifest.xml | 5 +- .../Caboodle.DeviceTests.Shared.projitems | 1 + .../Connectivity_Tests.cs | 17 ++ .../Properties/AndroidManifest.xml | 3 +- .../View/ConnectivityPage.xaml | 23 ++ .../View/ConnectivityPage.xaml.cs | 19 ++ .../ViewModel/ConnectivityViewModel.cs | 45 ++++ .../ViewModel/HomeViewModel.cs | 1 + .../Microsoft.Caboodle/ConnectionProfile.xml | 109 ++++++++ docs/en/Microsoft.Caboodle/Connectivity.xml | 81 ++++++ .../ConnectivityChangedEventArgs.xml | 56 +++++ .../ConnectivityChangedEventHandler.xml | 24 ++ docs/en/Microsoft.Caboodle/NetworkAccess.xml | 92 +++++++ 24 files changed, 1240 insertions(+), 9 deletions(-) create mode 100644 Caboodle.Tests/Connectivity_Tests.cs create mode 100644 Caboodle/Connectivity/Connectivity.android.cs create mode 100644 Caboodle/Connectivity/Connectivity.ios.cs create mode 100644 Caboodle/Connectivity/Connectivity.ios.reachability.cs create mode 100644 Caboodle/Connectivity/Connectivity.netstandard.cs create mode 100644 Caboodle/Connectivity/Connectivity.shared.cs create mode 100644 Caboodle/Connectivity/Connectivity.shared.enums.cs create mode 100644 Caboodle/Connectivity/Connectivity.uwp.cs create mode 100644 DeviceTests/Caboodle.DeviceTests.Shared/Connectivity_Tests.cs create mode 100644 Samples/Caboodle.Samples/View/ConnectivityPage.xaml create mode 100644 Samples/Caboodle.Samples/View/ConnectivityPage.xaml.cs create mode 100644 Samples/Caboodle.Samples/ViewModel/ConnectivityViewModel.cs create mode 100644 docs/en/Microsoft.Caboodle/ConnectionProfile.xml create mode 100644 docs/en/Microsoft.Caboodle/Connectivity.xml create mode 100644 docs/en/Microsoft.Caboodle/ConnectivityChangedEventArgs.xml create mode 100644 docs/en/Microsoft.Caboodle/ConnectivityChangedEventHandler.xml create mode 100644 docs/en/Microsoft.Caboodle/NetworkAccess.xml diff --git a/Caboodle.Tests/Connectivity_Tests.cs b/Caboodle.Tests/Connectivity_Tests.cs new file mode 100644 index 00000000000..273b2a4bdaa --- /dev/null +++ b/Caboodle.Tests/Connectivity_Tests.cs @@ -0,0 +1,24 @@ +using Microsoft.Caboodle; +using Xunit; + +namespace Caboodle.Tests +{ + public class Connectivity_Tests + { + [Fact] + public void Network_Access_On_NetStandard() => + Assert.Throws(() => Connectivity.NetworkAccess); + + [Fact] + public void Profiles_On_NetStandard() => + Assert.Throws(() => Connectivity.Profiles); + + [Fact] + public void Connectivity_Changed_Event_On_NetStandard() => + Assert.Throws(() => Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged); + + void Connectivity_ConnectivityChanged(ConnectivityChangedEventArgs e) + { + } + } +} diff --git a/Caboodle/Caboodle.csproj b/Caboodle/Caboodle.csproj index 6a463f116c7..707a88bf89b 100644 --- a/Caboodle/Caboodle.csproj +++ b/Caboodle/Caboodle.csproj @@ -47,6 +47,7 @@ + @@ -54,13 +55,16 @@ Windows Mobile Extensions for the UWP + + + diff --git a/Caboodle/Clipboard/Clipboard.android.cs b/Caboodle/Clipboard/Clipboard.android.cs index 9118fa4d873..64e372f56a4 100644 --- a/Caboodle/Clipboard/Clipboard.android.cs +++ b/Caboodle/Clipboard/Clipboard.android.cs @@ -6,16 +6,13 @@ namespace Microsoft.Caboodle { public static partial class Clipboard { - static ClipboardManager ClipboardManager - => (ClipboardManager)Application.Context.GetSystemService(Context.ClipboardService); - public static void SetText(string text) - => ClipboardManager.PrimaryClip = ClipData.NewPlainText("Text", text); + => Platform.ClipboardManager.PrimaryClip = ClipData.NewPlainText("Text", text); public static bool HasText - => ClipboardManager.HasPrimaryClip; + => Platform.ClipboardManager.HasPrimaryClip; public static Task GetTextAsync() - => Task.FromResult(ClipboardManager.PrimaryClip?.GetItemAt(0)?.Text); + => Task.FromResult(Platform.ClipboardManager.PrimaryClip?.GetItemAt(0)?.Text); } } diff --git a/Caboodle/Connectivity/Connectivity.android.cs b/Caboodle/Connectivity/Connectivity.android.cs new file mode 100644 index 00000000000..a41a48172e7 --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.android.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Android; +using Android.App; +using Android.Content; +using Android.Net; +using Android.Net.Wifi; +using Android.OS; + +namespace Microsoft.Caboodle +{ + public partial class Connectivity + { + static ConnectivityBroadcastReceiver conectivityReceiver; + static bool hasPermission; + + static void ValidatePermission() + { + if (hasPermission) + return; + + var permission = Manifest.Permission.AccessNetworkState; + if (!Platform.HasPermissionInManifest(permission)) + throw new PermissionException(permission); + + hasPermission = true; + } + + static void StartListeners() + { + ValidatePermission(); + conectivityReceiver = new ConnectivityBroadcastReceiver(OnConnectivityChanged); + Platform.CurrentContext.RegisterReceiver(conectivityReceiver, new IntentFilter(ConnectivityManager.ConnectivityAction)); + } + + static void StopListeners() + { + Platform.CurrentContext.UnregisterReceiver(conectivityReceiver); + conectivityReceiver?.Dispose(); + conectivityReceiver = null; + } + + static NetworkAccess IsBetterAccess(NetworkAccess currentAccess, NetworkAccess newAccess) => + newAccess > currentAccess ? newAccess : currentAccess; + + public static NetworkAccess NetworkAccess + { + get + { + ValidatePermission(); + try + { + var currentAccess = NetworkAccess.None; + var manager = Platform.ConnectivityManager; + + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + { + foreach (var network in manager.GetAllNetworks()) + { + try + { + var capabilities = manager.GetNetworkCapabilities(network); + + if (capabilities == null) + continue; + + // Check to see if it has the internet capability + if (!capabilities.HasCapability(NetCapability.Internet)) + { + // Doesn't have internet, but local is possible + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Local); + continue; + } + + var info = manager.GetNetworkInfo(network); + + ProcessNetworkInfo(info); + } + catch + { + // there is a possibility, but don't worry + } + } + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var info in manager.GetAllNetworkInfo()) +#pragma warning restore CS0618 // Type or member is obsolete + { + ProcessNetworkInfo(info); + } + } + + void ProcessNetworkInfo(NetworkInfo info) + { + if (info == null || !info.IsAvailable) + return; + + if (info.IsConnected) + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.Internet); + else if (info.IsConnectedOrConnecting) + currentAccess = IsBetterAccess(currentAccess, NetworkAccess.ConstrainedInternet); + } + + return currentAccess; + } + catch (Exception e) + { + Console.WriteLine("Unable to get connected state - do you have ACCESS_NETWORK_STATE permission? - error: {0}", e); + return NetworkAccess.Unknown; + } + } + } + + public static IEnumerable Profiles + { + get + { + ValidatePermission(); + var manager = Platform.ConnectivityManager; + if (Platform.HasApiLevel(BuildVersionCodes.Lollipop)) + { + foreach (var network in manager.GetAllNetworks()) + { + NetworkInfo info = null; + try + { + info = manager.GetNetworkInfo(network); + } + catch + { + // there is a possibility, but don't worry about it + } + + var p = ProcessNetworkInfo(info); + if (p.HasValue) + yield return p.Value; + } + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var info in manager.GetAllNetworkInfo()) +#pragma warning restore CS0618 // Type or member is obsolete + { + var p = ProcessNetworkInfo(info); + if (p.HasValue) + yield return p.Value; + } + } + + ConnectionProfile? ProcessNetworkInfo(NetworkInfo info) + { + if (info == null || !info.IsAvailable || !info.IsConnectedOrConnecting) + return null; + + return GetConnectionType(info.Type, info.TypeName); + } + } + } + + internal static ConnectionProfile GetConnectionType(ConnectivityType connectivityType, string typeName) + { + switch (connectivityType) + { + case ConnectivityType.Ethernet: + return ConnectionProfile.Ethernet; + case ConnectivityType.Wimax: + return ConnectionProfile.WiMAX; + case ConnectivityType.Wifi: + return ConnectionProfile.WiFi; + case ConnectivityType.Bluetooth: + return ConnectionProfile.Bluetooth; + case ConnectivityType.Mobile: + case ConnectivityType.MobileDun: + case ConnectivityType.MobileHipri: + case ConnectivityType.MobileMms: + return ConnectionProfile.Cellular; + case ConnectivityType.Dummy: + return ConnectionProfile.Other; + default: + if (string.IsNullOrWhiteSpace(typeName)) + return ConnectionProfile.Other; + + var typeNameLower = typeName.ToLowerInvariant(); + if (typeNameLower.Contains("mobile")) + return ConnectionProfile.Cellular; + + if (typeNameLower.Contains("wifi")) + return ConnectionProfile.WiFi; + + if (typeNameLower.Contains("wimax")) + return ConnectionProfile.WiMAX; + + if (typeNameLower.Contains("ethernet")) + return ConnectionProfile.Ethernet; + + if (typeNameLower.Contains("bluetooth")) + return ConnectionProfile.Bluetooth; + + return ConnectionProfile.Other; + } + } + } + + class ConnectivityBroadcastReceiver : BroadcastReceiver + { + Action onChanged; + + public ConnectivityBroadcastReceiver(Action onChanged) => + this.onChanged = onChanged; + + public override async void OnReceive(Context context, Intent intent) + { + if (intent.Action != ConnectivityManager.ConnectivityAction) + return; + + // await 500ms to ensure that the the connection manager updates + await Task.Delay(500); + onChanged?.Invoke(); + } + } +} diff --git a/Caboodle/Connectivity/Connectivity.ios.cs b/Caboodle/Connectivity/Connectivity.ios.cs new file mode 100644 index 00000000000..6f694831070 --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.ios.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Caboodle +{ + public static partial class Connectivity + { + static void StartListeners() => + Reachability.ReachabilityChanged += ReachabilityChanged; + + static async void ReachabilityChanged(object sender, EventArgs e) + { + await Task.Delay(100); + OnConnectivityChanged(); + } + + static void StopListeners() => + Reachability.ReachabilityChanged -= ReachabilityChanged; + + public static NetworkAccess NetworkAccess + { + get + { + var remoteHostStatus = Reachability.RemoteHostStatus(); + var internetStatus = Reachability.InternetConnectionStatus(); + + var isConnected = (internetStatus == NetworkStatus.ReachableViaCarrierDataNetwork || + internetStatus == NetworkStatus.ReachableViaWiFiNetwork) || + (remoteHostStatus == NetworkStatus.ReachableViaCarrierDataNetwork || + remoteHostStatus == NetworkStatus.ReachableViaWiFiNetwork); + + return isConnected ? NetworkAccess.Internet : NetworkAccess.None; + } + } + + public static IEnumerable Profiles + { + get + { + var statuses = Reachability.GetActiveConnectionType(); + foreach (var status in statuses) + { + switch (status) + { + case NetworkStatus.ReachableViaCarrierDataNetwork: + yield return ConnectionProfile.Cellular; + break; + case NetworkStatus.ReachableViaWiFiNetwork: + yield return ConnectionProfile.WiFi; + break; + default: + yield return ConnectionProfile.Other; + break; + } + } + } + } + } +} diff --git a/Caboodle/Connectivity/Connectivity.ios.reachability.cs b/Caboodle/Connectivity/Connectivity.ios.reachability.cs new file mode 100644 index 00000000000..25876551c40 --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.ios.reachability.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading.Tasks; +using CoreFoundation; +using SystemConfiguration; + +namespace Microsoft.Caboodle +{ + internal enum NetworkStatus + { + NotReachable, + ReachableViaCarrierDataNetwork, + ReachableViaWiFiNetwork + } + + internal static class Reachability + { + static string hostName = "www.microsoft.com"; + + /// + /// Checks if reachable without requiring a connection + /// + /// + /// + internal 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; + } + + /// + /// Checks if host is reachable + /// + /// + /// + /// + internal static bool IsHostReachable(string host, int port) + { + if (string.IsNullOrWhiteSpace(host)) + return false; + + if (!IPAddress.TryParse(host + ":" + port, out var address)) + { + Debug.WriteLine(host + ":" + port + " is not valid"); + return false; + } + using (var r = new NetworkReachability(host)) + { + if (r.TryGetFlags(out var flags)) + { + return IsReachableWithoutRequiringConnection(flags); + } + } + return false; + } + + /// + /// Is the host reachable with the current network configuration + /// + /// + /// + internal static bool IsHostReachable(string host) + { + if (string.IsNullOrWhiteSpace(host)) + return false; + + using (var r = new NetworkReachability(host)) + { + if (r.TryGetFlags(out var flags)) + { + return IsReachableWithoutRequiringConnection(flags); + } + } + return false; + } + + /// + /// Raised every time there is an interesting reachable event, + /// we do not even pass the info as to what changed, and + /// we lump all three status we probe into one + /// + internal static event EventHandler ReachabilityChanged; + + static async void OnChange(NetworkReachabilityFlags flags) + { + await Task.Delay(100); + ReachabilityChanged?.Invoke(null, EventArgs.Empty); + } + + static NetworkReachability defaultRouteReachability; + + static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) + { + if (defaultRouteReachability == null) + { + var ip = new IPAddress(0); + defaultRouteReachability = new NetworkReachability(ip); + defaultRouteReachability.SetNotification(OnChange); + defaultRouteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + } + if (!defaultRouteReachability.TryGetFlags(out flags)) + return false; + return IsReachableWithoutRequiringConnection(flags); + } + + static NetworkReachability remoteHostReachability; + + internal static NetworkStatus RemoteHostStatus() + { + NetworkReachabilityFlags flags; + bool reachable; + + if (remoteHostReachability == null) + { + remoteHostReachability = 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 + reachable = remoteHostReachability.TryGetFlags(out flags); + + remoteHostReachability.SetNotification(OnChange); + remoteHostReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); + } + else + { + reachable = remoteHostReachability.TryGetFlags(out flags); + } + + if (!reachable) + return NetworkStatus.NotReachable; + + if (!IsReachableWithoutRequiringConnection(flags)) + return NetworkStatus.NotReachable; + + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + return NetworkStatus.ReachableViaCarrierDataNetwork; + + return NetworkStatus.ReachableViaWiFiNetwork; + } + + /// + /// Checks internet connection status + /// + /// + internal static IEnumerable GetActiveConnectionType() + { + var status = new List(); + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + { + status.Add(NetworkStatus.ReachableViaCarrierDataNetwork); + } + else if (defaultNetworkAvailable) + { + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + else if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 + || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) + && (flags & NetworkReachabilityFlags.InterventionRequired) == 0) + { + // If the connection is on-demand or on-traffic and no user intervention + // is required, then assume WiFi. + status.Add(NetworkStatus.ReachableViaWiFiNetwork); + } + + return status; + } + + /// + /// Checks internet connection status + /// + /// + public static NetworkStatus InternetConnectionStatus() + { + var status = NetworkStatus.NotReachable; + + var defaultNetworkAvailable = IsNetworkAvailable(out var flags); + + // If it's a WWAN connection.. + if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) + status = NetworkStatus.ReachableViaCarrierDataNetwork; + + // If the connection is reachable and no connection is required, then assume it's WiFi + if (defaultNetworkAvailable) + { + status = NetworkStatus.ReachableViaWiFiNetwork; + } + + // 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; + } + + /// + /// Dispose + /// + internal static void Dispose() + { + if (remoteHostReachability != null) + { + remoteHostReachability.Dispose(); + remoteHostReachability = null; + } + + if (defaultRouteReachability != null) + { + defaultRouteReachability.Dispose(); + defaultRouteReachability = null; + } + } + } +} diff --git a/Caboodle/Connectivity/Connectivity.netstandard.cs b/Caboodle/Connectivity/Connectivity.netstandard.cs new file mode 100644 index 00000000000..8f8a9c8d793 --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.netstandard.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Caboodle +{ + public static partial class Connectivity + { + public static NetworkAccess NetworkAccess => + throw new NotImplementedInReferenceAssemblyException(); + + public static IEnumerable Profiles => + throw new NotImplementedInReferenceAssemblyException(); + + static void StartListeners() => + throw new NotImplementedInReferenceAssemblyException(); + + static void StopListeners() => + throw new NotImplementedInReferenceAssemblyException(); + } +} diff --git a/Caboodle/Connectivity/Connectivity.shared.cs b/Caboodle/Connectivity/Connectivity.shared.cs new file mode 100644 index 00000000000..ea60896c8cf --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.shared.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Caboodle +{ + public static partial class Connectivity + { + static event ConnectivityChangedEventHandler ConnectivityChanagedInternal; + + static NetworkAccess currentAccess; + + static List currentProfiles; + + public static event ConnectivityChangedEventHandler ConnectivityChanged + { + add + { + var wasRunning = ConnectivityChanagedInternal != null; + + ConnectivityChanagedInternal += value; + + if (!wasRunning && ConnectivityChanagedInternal != null) + { + SetCurrent(); + StartListeners(); + } + } + + remove + { + var wasRunning = ConnectivityChanagedInternal != null; + + ConnectivityChanagedInternal -= value; + + if (wasRunning && ConnectivityChanagedInternal == null) + StopListeners(); + } + } + + static void SetCurrent() + { + currentAccess = NetworkAccess; + currentProfiles = new List(Profiles); + } + + static void OnConnectivityChanged(NetworkAccess access, IEnumerable profiles) + => OnConnectivityChanged(new ConnectivityChangedEventArgs(access, profiles)); + + static void OnConnectivityChanged() + => OnConnectivityChanged(NetworkAccess, Profiles); + + static void OnConnectivityChanged(ConnectivityChangedEventArgs e) + { + if (currentAccess != e.NetworkAccess || + !currentProfiles.SequenceEqual(e.Profiles)) + { + SetCurrent(); + Platform.BeginInvokeOnMainThread(() => ConnectivityChanagedInternal?.Invoke(e)); + } + } + } + + public delegate void ConnectivityChangedEventHandler(ConnectivityChangedEventArgs e); + + public class ConnectivityChangedEventArgs : EventArgs + { + internal ConnectivityChangedEventArgs(NetworkAccess access, IEnumerable profiles) + { + NetworkAccess = access; + Profiles = profiles; + } + + public NetworkAccess NetworkAccess { get; } + + public IEnumerable Profiles { get; } + } +} diff --git a/Caboodle/Connectivity/Connectivity.shared.enums.cs b/Caboodle/Connectivity/Connectivity.shared.enums.cs new file mode 100644 index 00000000000..8ed58038fcb --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.shared.enums.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Caboodle +{ + public enum ConnectionProfile + { + Bluetooth, + Cellular, + Ethernet, + WiMAX, + WiFi, + Other + } + + public enum NetworkAccess + { + Unknown = 0, + None = 1, + Local = 2, + ConstrainedInternet = 3, + Internet = 4 + } +} diff --git a/Caboodle/Connectivity/Connectivity.uwp.cs b/Caboodle/Connectivity/Connectivity.uwp.cs new file mode 100644 index 00000000000..3080dd135a4 --- /dev/null +++ b/Caboodle/Connectivity/Connectivity.uwp.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Windows.Networking.Connectivity; + +namespace Microsoft.Caboodle +{ + public static partial class Connectivity + { + static void StartListeners() => + NetworkInformation.NetworkStatusChanged += NetworkStatusChanged; + + static void NetworkStatusChanged(object sender) => + OnConnectivityChanged(); + + static void StopListeners() => + NetworkInformation.NetworkStatusChanged -= NetworkStatusChanged; + + public static NetworkAccess NetworkAccess + { + get + { + var profile = NetworkInformation.GetInternetConnectionProfile(); + if (profile == null) + return NetworkAccess.Unknown; + + var level = profile.GetNetworkConnectivityLevel(); + switch (level) + { + case NetworkConnectivityLevel.LocalAccess: + return NetworkAccess.Local; + case NetworkConnectivityLevel.InternetAccess: + return NetworkAccess.Internet; + case NetworkConnectivityLevel.ConstrainedInternetAccess: + return NetworkAccess.ConstrainedInternet; + default: + return NetworkAccess.None; + } + } + } + + public static IEnumerable Profiles + { + get + { + var networkInterfaceList = NetworkInformation.GetConnectionProfiles(); + foreach (var networkInterfaceInfo in networkInterfaceList.Where(networkInterfaceInfo => networkInterfaceInfo.GetNetworkConnectivityLevel() != NetworkConnectivityLevel.None)) + { + var type = ConnectionProfile.Other; + + if (networkInterfaceInfo.NetworkAdapter != null) + { + // http://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib + switch (networkInterfaceInfo.NetworkAdapter.IanaInterfaceType) + { + case 6: + type = ConnectionProfile.Ethernet; + break; + case 71: + type = ConnectionProfile.WiFi; + break; + case 243: + case 244: + type = ConnectionProfile.Cellular; + break; + + // xbox wireless, can skip + case 281: + continue; + } + } + + yield return type; + } + } + } + } +} diff --git a/Caboodle/Platform/Platform.android.cs b/Caboodle/Platform/Platform.android.cs index cece987012f..03a9c45274f 100644 --- a/Caboodle/Platform/Platform.android.cs +++ b/Caboodle/Platform/Platform.android.cs @@ -3,6 +3,8 @@ using Android.App; using Android.Content; using Android.Content.PM; +using Android.Net; +using Android.Net.Wifi; using Android.OS; namespace Microsoft.Caboodle @@ -53,6 +55,15 @@ public static void BeginInvokeOnMainThread(Action action) handler.Post(action); } + + internal static ClipboardManager ClipboardManager + => (ClipboardManager)Application.Context.GetSystemService(Context.ClipboardService); + + internal static ConnectivityManager ConnectivityManager => + (ConnectivityManager)Application.Context.GetSystemService(Context.ConnectivityService); + + internal static WifiManager WifiManager => + (WifiManager)Application.Context.GetSystemService(Context.WifiService); } class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks diff --git a/DeviceTests/Caboodle.DeviceTests.Android/Properties/AndroidManifest.xml b/DeviceTests/Caboodle.DeviceTests.Android/Properties/AndroidManifest.xml index 6e5e21059ff..3abbf6bef86 100644 --- a/DeviceTests/Caboodle.DeviceTests.Android/Properties/AndroidManifest.xml +++ b/DeviceTests/Caboodle.DeviceTests.Android/Properties/AndroidManifest.xml @@ -1,6 +1,7 @@  - + + - + \ No newline at end of file diff --git a/DeviceTests/Caboodle.DeviceTests.Shared/Caboodle.DeviceTests.Shared.projitems b/DeviceTests/Caboodle.DeviceTests.Shared/Caboodle.DeviceTests.Shared.projitems index 7ae5978dc1b..2bc7edfbf63 100644 --- a/DeviceTests/Caboodle.DeviceTests.Shared/Caboodle.DeviceTests.Shared.projitems +++ b/DeviceTests/Caboodle.DeviceTests.Shared/Caboodle.DeviceTests.Shared.projitems @@ -9,6 +9,7 @@ Caboodle.DeviceTests + diff --git a/DeviceTests/Caboodle.DeviceTests.Shared/Connectivity_Tests.cs b/DeviceTests/Caboodle.DeviceTests.Shared/Connectivity_Tests.cs new file mode 100644 index 00000000000..dce199b043e --- /dev/null +++ b/DeviceTests/Caboodle.DeviceTests.Shared/Connectivity_Tests.cs @@ -0,0 +1,17 @@ +using System.Linq; +using Microsoft.Caboodle; +using Xunit; + +namespace Caboodle.DeviceTests +{ + public class Connectivity_Tests + { + [Fact] + public void Network_Access() => + Assert.Equal(NetworkAccess.Internet, Connectivity.NetworkAccess); + + [Fact] + public void Profiles() => + Assert.True(Connectivity.Profiles.Count() > 0); + } +} diff --git a/Samples/Caboodle.Samples.Android/Properties/AndroidManifest.xml b/Samples/Caboodle.Samples.Android/Properties/AndroidManifest.xml index 2d385657518..02285a8b6d3 100644 --- a/Samples/Caboodle.Samples.Android/Properties/AndroidManifest.xml +++ b/Samples/Caboodle.Samples.Android/Properties/AndroidManifest.xml @@ -1,6 +1,7 @@  - + + \ No newline at end of file diff --git a/Samples/Caboodle.Samples/View/ConnectivityPage.xaml b/Samples/Caboodle.Samples/View/ConnectivityPage.xaml new file mode 100644 index 00000000000..0bc931ba07f --- /dev/null +++ b/Samples/Caboodle.Samples/View/ConnectivityPage.xaml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/Samples/Caboodle.Samples/View/ConnectivityPage.xaml.cs b/Samples/Caboodle.Samples/View/ConnectivityPage.xaml.cs new file mode 100644 index 00000000000..b683f840dc3 --- /dev/null +++ b/Samples/Caboodle.Samples/View/ConnectivityPage.xaml.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Caboodle.Samples.View +{ + public partial class ConnectivityPage : BasePage + { + public ConnectivityPage() + { + InitializeComponent(); + } + } +} diff --git a/Samples/Caboodle.Samples/ViewModel/ConnectivityViewModel.cs b/Samples/Caboodle.Samples/ViewModel/ConnectivityViewModel.cs new file mode 100644 index 00000000000..eaf71519daf --- /dev/null +++ b/Samples/Caboodle.Samples/ViewModel/ConnectivityViewModel.cs @@ -0,0 +1,45 @@ +using Microsoft.Caboodle; + +namespace Caboodle.Samples.ViewModel +{ + public class ConnectivityViewModel : BaseViewModel + { + public ConnectivityViewModel() + { + } + + public string NetworkAccess => + Connectivity.NetworkAccess.ToString(); + + public string Profiles + { + get + { + var profiles = string.Empty; + foreach (var p in Connectivity.Profiles) + profiles += "\n" + p.ToString(); + return profiles; + } + } + + public override void OnAppearing() + { + base.OnAppearing(); + + Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; + } + + void Connectivity_ConnectivityChanged(ConnectivityChangedEventArgs e) + { + OnPropertyChanged(nameof(Profiles)); + OnPropertyChanged(nameof(NetworkAccess)); + } + + public override void OnDisappearing() + { + Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged; + + base.OnDisappearing(); + } + } +} diff --git a/Samples/Caboodle.Samples/ViewModel/HomeViewModel.cs b/Samples/Caboodle.Samples/ViewModel/HomeViewModel.cs index 1b1e75027d9..84b46a5bcff 100644 --- a/Samples/Caboodle.Samples/ViewModel/HomeViewModel.cs +++ b/Samples/Caboodle.Samples/ViewModel/HomeViewModel.cs @@ -13,6 +13,7 @@ public HomeViewModel() new SampleItem("Battery", typeof(BatteryPage), "Easily detect battery level, source, and state."), new SampleItem("Browser", typeof(BrowserPage), "Quickly and easily open a browser to a specific website."), new SampleItem("Clipboard", typeof(ClipboardPage), "Quickly and easily use clipboard"), + new SampleItem("Connectivity", typeof(ConnectivityPage), "Check connectivity state and detect changes."), new SampleItem("Data Transfer", typeof(DataTransferPage), "Send text and website uris to other apps."), new SampleItem("Device Info", typeof(DeviceInfoPage), "Find out about the device with ease."), new SampleItem("File System", typeof(FileSystemPage), "Easily save files to app data."), diff --git a/docs/en/Microsoft.Caboodle/ConnectionProfile.xml b/docs/en/Microsoft.Caboodle/ConnectionProfile.xml new file mode 100644 index 00000000000..2fec2cb9c56 --- /dev/null +++ b/docs/en/Microsoft.Caboodle/ConnectionProfile.xml @@ -0,0 +1,109 @@ + + + + + Microsoft.Caboodle + 1.0.0.0 + + + System.Enum + + + Describes the type of connection the device is using. + + + + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 0 + + The bluetooth data connection. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 1 + + The mobile/cellular data connection. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 2 + + The ethernet data connection. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 5 + + Other non-known type of connection. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 4 + + The WiFi data connection. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectionProfile + + 3 + + The WiMAX data connection. + + + + diff --git a/docs/en/Microsoft.Caboodle/Connectivity.xml b/docs/en/Microsoft.Caboodle/Connectivity.xml new file mode 100644 index 00000000000..7bc30e4050b --- /dev/null +++ b/docs/en/Microsoft.Caboodle/Connectivity.xml @@ -0,0 +1,81 @@ + + + + + Microsoft.Caboodle + 1.0.0.0 + + + System.Object + + + + Connectivity and networking helpers. + + + + + + + + + Event + + 1.0.0.0 + + + Microsoft.Caboodle.ConnectivityChangedEventHandler + + + + Event that is triggered when a network access or profile has changed. + + + Can throw PermissionException on Android if ACCESS_NETWORK_STATE is not set in manifest. + + + + + + + + Property + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + + Gets the current state of network access. Does not guarantee full access to the internet. + The current network access state. + + Can throw PermissionException on Android if ACCESS_NETWORK_STATE is not set in manifest. + + + + + + + Property + + 1.0.0.0 + + + + get: System.Runtime.CompilerServices.IteratorStateMachine(typeof(Microsoft.Caboodle.Connectivity/<get_Profiles>d__18)) + + + + System.Collections.Generic.IEnumerable<Microsoft.Caboodle.ConnectionProfile> + + + Gets the active connectivity types for the device. + List of all connection profiles. + + Can throw PermissionException on Android if ACCESS_NETWORK_STATE is not set in manifest. + + + + + diff --git a/docs/en/Microsoft.Caboodle/ConnectivityChangedEventArgs.xml b/docs/en/Microsoft.Caboodle/ConnectivityChangedEventArgs.xml new file mode 100644 index 00000000000..40ad627fbef --- /dev/null +++ b/docs/en/Microsoft.Caboodle/ConnectivityChangedEventArgs.xml @@ -0,0 +1,56 @@ + + + + + Microsoft.Caboodle + 1.0.0.0 + + + System.EventArgs + + + + The current connectivity information from the change event. + + + + + + + + + Property + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + + Gets the current state of network access. Does not guarantee full access to the internet. + The current network access state. + + + + + + + + + Property + + 1.0.0.0 + + + System.Collections.Generic.IEnumerable<Microsoft.Caboodle.ConnectionProfile> + + + Gets the active connectivity types for the device. + List of all connection profiles. + + + + + + + diff --git a/docs/en/Microsoft.Caboodle/ConnectivityChangedEventHandler.xml b/docs/en/Microsoft.Caboodle/ConnectivityChangedEventHandler.xml new file mode 100644 index 00000000000..9ec6a035b7e --- /dev/null +++ b/docs/en/Microsoft.Caboodle/ConnectivityChangedEventHandler.xml @@ -0,0 +1,24 @@ + + + + + Microsoft.Caboodle + 1.0.0.0 + + + System.Delegate + + + + + + System.Void + + + Connectivity changed event information. + Event handler for connectivity change events. + + + + + diff --git a/docs/en/Microsoft.Caboodle/NetworkAccess.xml b/docs/en/Microsoft.Caboodle/NetworkAccess.xml new file mode 100644 index 00000000000..6f11152a9e0 --- /dev/null +++ b/docs/en/Microsoft.Caboodle/NetworkAccess.xml @@ -0,0 +1,92 @@ + + + + + Microsoft.Caboodle + 1.0.0.0 + + + System.Enum + + + To be added. + To be added. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + 3 + + To be added. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + 4 + + To be added. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + 2 + + To be added. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + 1 + + To be added. + + + + + + Field + + 1.0.0.0 + + + Microsoft.Caboodle.NetworkAccess + + 0 + + To be added. + + + +