diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 6b1b3780c4c2..dce40aad7182 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,7 +1,10 @@ -## 0.4.5 - * Support the v2 Android embedder. +## 0.4.4+2 + +* Change `ConnectionResult` type from enum to class with 2 fields. `type:ConnectionType` and `subtype:ConnectionSubtype` +* Add `getNetworkSubtype` to get mobile connection subtype. + ## 0.4.4+1 * Update and migrate iOS example project. diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index c26def4e8ea4..731c92e4ba75 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -14,14 +14,33 @@ Sample usage to check current status: ```dart import 'package:connectivity/connectivity.dart'; -var connectivityResult = await (Connectivity().checkConnectivity()); -if (connectivityResult == ConnectivityResult.mobile) { +var connectivityInfo = await (Connectivity().checkConnectivityInfo()); +if (connectivityInfo.result == ConnectivityResult.mobile) { // I am connected to a mobile network. -} else if (connectivityResult == ConnectivityResult.wifi) { + if(connectivityInfo.subtype == ConnectionSubtype.HSDPA){ + // I am on an HSDPA network + } +} else if (connectivityInfo.result == ConnectivityResult.wifi) { // I am connected to a wifi network. } ``` +You can also check for a connection type when you are on mobile: + +```dart +import 'package:connectivity/connectivity.dart'; + +var connectivityResult = await (Connectivity().getNetworkSubtype()); + +if(connectivityResult.subtype == ConnectionSubtype.edge){ + // I am on an edge network +} else if(connectivityResult.subtype == ConnectionSubtype.hsdpa){ + // I am on a hsdpa network +} else if(connectivityResult.subtype == ConnectionSubtype.lte){ + // I am on an lte network +} +``` + > Note that you should not be using the current network status for deciding whether you can reliably make a network connection. Always guard your app code against timeouts and errors that might come from the network layer. @@ -36,9 +55,11 @@ import 'package:connectivity/connectivity.dart'; initState() { super.initState(); - subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { + subscription = Connectivity().onConnectivityInfoChanged.listen((ConnectionInfo info) { // Got a new connectivity status! - }) + info.result = // ConnectivityResult info + info.subtype = // Mobile Connectivity Subtype info + }); } // Be sure to cancel subscription after you are done diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java index 605acdb73948..bf5c032478b9 100644 --- a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java @@ -11,6 +11,8 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; +import android.telephony.TelephonyManager; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -107,4 +109,76 @@ private String getNetworkTypeLegacy() { return "none"; } } + + @Nullable + String getNetworkSubType() { + + NetworkInfo info = this.connectivityManager.getActiveNetworkInfo(); + + if (info == null || !info.isConnected()) { + return null; + } + + /// Telephony Manager documentation https://developer.android.com/reference/android/telephony/TelephonyManager + /// Information about mobile broadband - https://en.wikipedia.org/wiki/Mobile_broadband#Generations + + switch (info.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_1xRTT: { + return "1xRTT"; // ~ 50-100 kbps + } + case TelephonyManager.NETWORK_TYPE_CDMA: { + return "cdma"; // ~ 14-64 kbps + } + case TelephonyManager.NETWORK_TYPE_EDGE: { + return "edge"; // ~ 50-100 kbps + } + case TelephonyManager.NETWORK_TYPE_EVDO_0: { + return "evdo_0"; // ~ 400-1000 kbps + } + case TelephonyManager.NETWORK_TYPE_EVDO_A: { + return "evdo_a"; // ~ 600-1400 kbps + } + case TelephonyManager.NETWORK_TYPE_GPRS: { + return "gprs"; // ~ 100 kbps + } + case TelephonyManager.NETWORK_TYPE_HSDPA: { + return "hsdpa"; // ~ 2-14 Mbps + } + case TelephonyManager.NETWORK_TYPE_HSPA: { + return "hspa"; // ~ 700-1700 kbps + } + case TelephonyManager.NETWORK_TYPE_HSUPA: { + return "hsupa"; // ~ 1-23 Mbps + } + case TelephonyManager.NETWORK_TYPE_UMTS: { + return "umts"; // ~ 400-7000 kbps + } + /* + * Above API level 7, make sure to set android:targetSdkVersion + * to appropriate level to use these + */ + case TelephonyManager.NETWORK_TYPE_EHRPD: { // API level 11 + return "ehrpd"; // ~ 1-2 Mbps + } + case TelephonyManager.NETWORK_TYPE_EVDO_B: { // API level 9 + return "evdo_b"; // ~ 5 Mbps + } + case TelephonyManager.NETWORK_TYPE_HSPAP: { // API level 13 + return "hspap"; // ~ 10-20 Mbps + } + case TelephonyManager.NETWORK_TYPE_IDEN: { // API level 8 + return "iden"; // ~25 kbps + } + case TelephonyManager.NETWORK_TYPE_LTE: { // API level 11 + return "lte"; // ~ 10+ Mbps + } + // Unknown + case TelephonyManager.NETWORK_TYPE_UNKNOWN: { + return "unknown"; // is connected but cannot tell the speed + } + default: { + return null; + } + } + } } diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java index 488c8efdd15f..9f0b04ad28d8 100644 --- a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java @@ -41,6 +41,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { case "wifiIPAddress": result.success(connectivity.getWifiIPAddress()); break; + case "subtype": + result.success(connectivity.getNetworkSubType()); + break; default: result.notImplemented(); break; diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java index ef8f7861d8e0..6557c8fba0fc 100644 --- a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java @@ -7,6 +7,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; + import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; @@ -41,7 +42,7 @@ private void setupChannels(BinaryMessenger messenger, Context context) { eventChannel = new EventChannel(messenger, "plugins.flutter.io/connectivity_status"); ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); Connectivity connectivity = new Connectivity(connectivityManager, wifiManager); @@ -60,4 +61,4 @@ private void teardownChannels() { methodChannel = null; eventChannel = null; } -} +} \ No newline at end of file diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index c01a110efb60..3d7957ae9d12 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -149,6 +149,11 @@ class _MyHomePageState extends State { }); break; case ConnectivityResult.mobile: + setState(() { + _connectionStatus = '$result\n' + 'Mobile Connection Type: $result\n'; + }); + break; case ConnectivityResult.none: setState(() => _connectionStatus = result.toString()); break; diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index c69871175b01..105c81fb78c6 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -14,10 +14,16 @@ #include +<<<<<<< HEAD +#import + +@interface FLTConnectivityPlugin () +======= @interface FLTConnectivityPlugin () @property(strong, nonatomic) FLTConnectivityLocationHandler* locationHandler; +>>>>>>> 0a7535d1cd7119767d8d2506b2c9e3742f585fa8 @end @implementation FLTConnectivityPlugin { @@ -90,15 +96,50 @@ - (NSString*)getWifiIP { return address; } +- (NSString*)getConnectionSubtype:(Reachability*)reachability { + if ([reachability currentReachabilityStatus] == NotReachable) { + return @"none"; + } + + CTTelephonyNetworkInfo* netinfo = [[CTTelephonyNetworkInfo alloc] init]; + NSString* carrierType = netinfo.currentRadioAccessTechnology; + + if ([carrierType isEqualToString:CTRadioAccessTechnologyGPRS]) { + return @"gprs"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyEdge]) { + return @"edge"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyWCDMA]) { + return @"cdma"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyHSDPA]) { + return @"hsdpa"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyHSUPA]) { + return @"hsupa"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyCDMA1x]) { + return @"cdma"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0]) { + return @"evdo_0"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA]) { + return @"evdo_a"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB]) { + return @"evdo_b"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyeHRPD]) { + return @"ehrpd"; + } else if ([carrierType isEqualToString:CTRadioAccessTechnologyLTE]) { + return @"lte"; + } + return @"unknown"; +} + - (NSString*)statusFromReachability:(Reachability*)reachability { NetworkStatus status = [reachability currentReachabilityStatus]; + NSString* subtype = [self getConnectionSubtype:[Reachability reachabilityForInternetConnection]]; switch (status) { case NotReachable: - return @"none"; + return [NSString stringWithFormat:@"%@,%@", @"none", subtype]; case ReachableViaWiFi: - return @"wifi"; + return [NSString stringWithFormat:@"%@,%@", @"wifi", subtype]; case ReachableViaWWAN: - return @"mobile"; + return [NSString stringWithFormat:@"%@,%@", @"mobile", subtype]; } } @@ -116,6 +157,11 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result([self getBSSID]); } else if ([call.method isEqualToString:@"wifiIPAddress"]) { result([self getWifiIP]); +<<<<<<< HEAD + } else if ([call.method isEqualToString:@"subtype"]) { + result([self getConnectionSubtype:[Reachability reachabilityForInternetConnection]]); + +======= } else if ([call.method isEqualToString:@"getLocationServiceAuthorization"]) { result([self convertCLAuthorizationStatusToString:[FLTConnectivityLocationHandler locationAuthorizationStatus]]); @@ -128,6 +174,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { completion:^(CLAuthorizationStatus status) { result([weakSelf convertCLAuthorizationStatusToString:status]); }]; +>>>>>>> 0a7535d1cd7119767d8d2506b2c9e3742f585fa8 } else { result(FlutterMethodNotImplemented); } diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 03659f5455a9..731cd2424659 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -14,6 +14,56 @@ import 'package:meta/meta.dart'; /// Mobile: Device connected to cellular network /// None: Device not connected to any network enum ConnectivityResult { wifi, mobile, none } +enum ConnectionSubtype { + none, + unknown, + m1xRTT, // ~ 50-100 kbps + cdma, // ~ 14-64 kbps + edge, // ~ 50-100 kbps + evdo_0, // ~ 400-1000 kbps + evdo_a, // ~ 600-1400 kbps + gprs, // ~ 100 kbps + hsdpa, // ~ 2-14 Mbps + hspa, // ~ 700-1700 kbps + hsupa, // ~ 1-23 Mbps + umts, // ~ 400-7000 kbps + ehrpd, // ~ 1-2 Mbps + evdo_b, // ~ 5 Mbps + hspap, // ~ 10-20 Mbps + iden, // ~25 kbps + lte, // ~ 10+ Mbps +} + +Map connectionTypeMap = { + "1xRTT": ConnectionSubtype.m1xRTT, // ~ 50-100 kbps + "cdma": ConnectionSubtype.cdma, // ~ 14-64 kbps + "edge": ConnectionSubtype.edge, // ~ 50-100 kbps + "evdo_0": ConnectionSubtype.evdo_0, // ~ 400-1000 kbps + "evdo_a": ConnectionSubtype.evdo_a, // ~ 600-1400 kbps + "gprs": ConnectionSubtype.gprs, // ~ 100 kbps + "hsdpa": ConnectionSubtype.hsdpa, // ~ 2-14 Mbps + "hspa": ConnectionSubtype.hspa, // ~ 700-1700 kbps + "hsupa": ConnectionSubtype.hsupa, // ~ 1-23 Mbps + "umts": ConnectionSubtype.umts, // ~ 400-7000 kbps + "ehrpd": ConnectionSubtype.ehrpd, // ~ 1-2 Mbps + "evdo_b": ConnectionSubtype.evdo_b, // ~ 5 Mbps + "hspap": ConnectionSubtype.hspap, // ~ 10-20 Mbps + "iden": ConnectionSubtype.iden, // ~25 kbps + "lte": ConnectionSubtype.lte, // ~ 10+ Mbps + "unknown": + ConnectionSubtype.unknown, // is connected but cannot tell the speed + "none": ConnectionSubtype.none +}; + +class ConnectivityDetailedResult { + ConnectivityDetailedResult({ + this.result = ConnectivityResult.none, + this.subtype = ConnectionSubtype.none, + }); + + final ConnectivityResult result; + final ConnectionSubtype subtype; +} class Connectivity { /// Constructs a singleton instance of [Connectivity]. @@ -34,6 +84,7 @@ class Connectivity { static Connectivity _singleton; Stream _onConnectivityChanged; + Stream _onConnectivityInfoChanged; @visibleForTesting static const MethodChannel methodChannel = MethodChannel( @@ -45,25 +96,56 @@ class Connectivity { 'plugins.flutter.io/connectivity_status', ); - /// Fires whenever the connectivity state changes. + /// Fires whenever the connectivity state changes. Returns stream of [ConnectivityResult] Stream get onConnectivityChanged { if (_onConnectivityChanged == null) { - _onConnectivityChanged = eventChannel - .receiveBroadcastStream() - .map((dynamic event) => _parseConnectivityResult(event)); + _onConnectivityChanged = eventChannel.receiveBroadcastStream().map( + (dynamic event) => _parseConnectivityDetailedResult(event).result); } return _onConnectivityChanged; } + /// Fires whenever the connectivity state changes. Return stream of [ConnectivityDetailedResult] + Stream get onConnectivityInfoChanged { + if (_onConnectivityInfoChanged == null) { + _onConnectivityInfoChanged = eventChannel + .receiveBroadcastStream() + .map((dynamic event) => _parseConnectivityDetailedResult(event)); + } + return _onConnectivityInfoChanged; + } + /// Checks the connection status of the device. /// /// Do not use the result of this function to decide whether you can reliably /// make a network request. It only gives you the radio status. /// /// Instead listen for connectivity changes via [onConnectivityChanged] stream. + /// + /// You can also check the mobile broadband connectivity subtype via [getNetworkSubtype] Future checkConnectivity() async { final String result = await methodChannel.invokeMethod('check'); - return _parseConnectivityResult(result); + return _parseConnectivityDetailedResult(result).result; + } + + /// Checks connectivity info, [ConnectivityDetailedResult] + Future checkConnectivityInfo() async { + final String result = await methodChannel.invokeMethod('check'); + return _parseConnectivityDetailedResult(result); + } + + /// Checks the network mobile connection subtype of the device. + /// Returns the appropriate mobile connectivity subtype enum [ConnectionSubtype] such + /// as gprs, edge, hsdpa etc. + /// + /// More information on mobile connectivity types can be found at + /// https://en.wikipedia.org/wiki/Mobile_broadband#Generations + /// + /// Return [ConnectionSubtype.unknown] if it is connected but there is not connection subtype info. eg. Wifi + /// Returns [ConnectionSubtype.none] if there is no connection + Future getNetworkSubtype() async { + final String result = await methodChannel.invokeMethod('subtype'); + return _parseConnectionSubtype(result); } /// Obtains the wifi name (SSID) of the connected network @@ -215,6 +297,18 @@ class Connectivity { } } +ConnectivityDetailedResult _parseConnectivityDetailedResult(String state) { + final List split = state.split(","); + return ConnectivityDetailedResult( + result: _parseConnectivityResult(split[0]), + subtype: _parseConnectionSubtype(split[1]), + ); +} + +ConnectionSubtype _parseConnectionSubtype(String state) { + return connectionTypeMap[state] ?? ConnectionSubtype.unknown; +} + ConnectivityResult _parseConnectivityResult(String state) { switch (state) { case 'wifi': diff --git a/packages/connectivity/test/connectivity_e2e.dart b/packages/connectivity/test/connectivity_e2e.dart index 10c4bda34e0d..54f43f96fc3b 100644 --- a/packages/connectivity/test/connectivity_e2e.dart +++ b/packages/connectivity/test/connectivity_e2e.dart @@ -17,10 +17,12 @@ void main() { _connectivity = Connectivity(); }); - testWidgets('test connectivity result', (WidgetTester tester) async { - final ConnectivityResult result = await _connectivity.checkConnectivity(); + test('test connectivity result', (WidgetTester tester) async { + final ConnectivityDetailedResult result = + await _connectivity.checkConnectivityInfo(); expect(result, isNotNull); - switch (result) { + expect(result.subtype, ConnectionSubtype.unknown); + switch (result.result) { case ConnectivityResult.wifi: expect(_connectivity.getWifiName(), completes); expect(_connectivity.getWifiBSSID(), completes); diff --git a/packages/connectivity/test/connectivity_test.dart b/packages/connectivity/test/connectivity_test.dart index 892e7d0085c5..f228339cb930 100644 --- a/packages/connectivity/test/connectivity_test.dart +++ b/packages/connectivity/test/connectivity_test.dart @@ -18,7 +18,9 @@ void main() { log.add(methodCall); switch (methodCall.method) { case 'check': - return 'wifi'; + return 'wifi,unknown'; + case 'subtype': + return 'evdo_b'; case 'wifiName': return '1337wifi'; case 'wifiBSSID': @@ -43,7 +45,8 @@ void main() { // ignore: deprecated_member_use await BinaryMessages.handlePlatformMessage( Connectivity.eventChannel.name, - Connectivity.eventChannel.codec.encodeSuccessEnvelope('wifi'), + Connectivity.eventChannel.codec + .encodeSuccessEnvelope('mobile,hsdpa'), (_) {}, ); break; @@ -57,7 +60,14 @@ void main() { test('onConnectivityChanged', () async { final ConnectivityResult result = await Connectivity().onConnectivityChanged.first; - expect(result, ConnectivityResult.wifi); + expect(result, ConnectivityResult.mobile); + }); + + test('onConnectivityInfoChanged', () async { + final ConnectivityDetailedResult info = + await Connectivity().onConnectivityInfoChanged.first; + expect(info.result, ConnectivityResult.mobile); + expect(info.subtype, ConnectionSubtype.hsdpa); }); test('getWifiName', () async { @@ -146,5 +156,35 @@ void main() { ], ); }); + + test('checkConnectivityInfo', () async { + final ConnectivityDetailedResult info = + await Connectivity().checkConnectivityInfo(); + expect(info.result, ConnectivityResult.wifi); + expect(info.subtype, ConnectionSubtype.unknown); + expect( + log, + [ + isMethodCall( + 'check', + arguments: null, + ), + ], + ); + }); + + test('subtype', () async { + final ConnectionSubtype result = await Connectivity().getNetworkSubtype(); + expect(result, ConnectionSubtype.evdo_b); + expect( + log, + [ + isMethodCall( + 'subtype', + arguments: null, + ), + ], + ); + }); }); }