diff --git a/src/android/NetworkManager.java b/src/android/NetworkManager.java index 95c8f07..f24d0f7 100755 --- a/src/android/NetworkManager.java +++ b/src/android/NetworkManager.java @@ -25,19 +25,27 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.cordova.PluginResult; import org.apache.cordova.CordovaWebView; import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import android.Manifest; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.telephony.ServiceState; + +import androidx.core.app.ActivityCompat; import java.util.Locale; +import static android.telephony.PhoneStateListener.LISTEN_SERVICE_STATE; + public class NetworkManager extends CordovaPlugin { public static int NOT_REACHABLE = 0; @@ -51,12 +59,12 @@ public class NetworkManager extends CordovaPlugin { // Android L calls this Cellular, because I have no idea! public static final String CELLULAR = "cellular"; - // 2G network types + // 2G network types public static final String TWO_G = "2g"; public static final String GSM = "gsm"; public static final String GPRS = "gprs"; public static final String EDGE = "edge"; - // 3G network types + // 3G network types public static final String THREE_G = "3g"; public static final String CDMA = "cdma"; public static final String UMTS = "umts"; @@ -70,6 +78,11 @@ public class NetworkManager extends CordovaPlugin { public static final String LTE = "lte"; public static final String UMB = "umb"; public static final String HSPA_PLUS = "hspa+"; + + // 5G network types + public static final String FIVE_G = "5g"; + public static final String NR = "nr"; + // return type public static final String TYPE_UNKNOWN = "unknown"; public static final String TYPE_ETHERNET = "ethernet"; @@ -78,15 +91,21 @@ public class NetworkManager extends CordovaPlugin { public static final String TYPE_2G = "2g"; public static final String TYPE_3G = "3g"; public static final String TYPE_4G = "4g"; + public static final String TYPE_5G = "5g"; public static final String TYPE_NONE = "none"; + public static final int NETWORK_TYPE_NR = 20; + public static final int NETWORK_TYPE_LTE_CA = 19; + private static final String LOG_TAG = "NetworkManager"; private CallbackContext connectionCallbackContext; ConnectivityManager sockMan; BroadcastReceiver receiver; - private String lastTypeOfNetwork; + private String lastTypeOfNetwork = TYPE_UNKNOWN; + TelephonyManager telMan; + private static boolean isNrAvailable; /** * Sets the context of the Command. This can then be used to do things like @@ -98,6 +117,8 @@ public class NetworkManager extends CordovaPlugin { public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); this.sockMan = (ConnectivityManager) cordova.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + this.telMan = (TelephonyManager) cordova.getActivity().getSystemService(Context.TELEPHONY_SERVICE); + this.telMan.listen( phoneStateListener, LISTEN_SERVICE_STATE); this.connectionCallbackContext = null; this.registerConnectivityActionReceiver(); @@ -158,7 +179,7 @@ private void registerConnectivityActionReceiver() { public void onReceive(Context context, Intent intent) { // (The null check is for the ARM Emulator, please use Intel Emulator for better results) if (NetworkManager.this.webView != null) { - updateConnectionInfo(sockMan.getActiveNetworkInfo()); + updateConnectionInfo(sockMan.getActiveNetworkInfo(), true); } String connectionType; @@ -204,10 +225,10 @@ private void unregisterReceiver() { * @param info the current active network info * @return */ - private void updateConnectionInfo(NetworkInfo info) { + private void updateConnectionInfo(NetworkInfo info, boolean forceRefresh) { // send update to javascript "navigator.connection" // Jellybean sends its own info - String currentNetworkType = this.getTypeOfNetworkFallbackToTypeNoneIfNotConnected(info); + String currentNetworkType = this.getTypeOfNetworkFallbackToTypeNoneIfNotConnected(info, forceRefresh); if (currentNetworkType.equals(this.lastTypeOfNetwork)) { LOG.d(LOG_TAG, "Networkinfo state didn't change, there is no event propagated to the JavaScript side."); } else { @@ -222,16 +243,19 @@ private void updateConnectionInfo(NetworkInfo info) { * @param info the current active network info * @return type the type of network */ - private String getTypeOfNetworkFallbackToTypeNoneIfNotConnected(NetworkInfo info) { + private String getTypeOfNetworkFallbackToTypeNoneIfNotConnected(NetworkInfo info, Boolean forceRefresh) { // the info might still be null in this part of the code String type; if (info != null) { // If we are not connected to any network set type to none if (!info.isConnected()) { type = TYPE_NONE; - } - else { - type = getType(info); + } else { + if(lastTypeOfNetwork.equals(TYPE_UNKNOWN) || forceRefresh) { + type = getType(info); + } else { + type = lastTypeOfNetwork; + } } } else { type = TYPE_NONE; @@ -240,6 +264,10 @@ private String getTypeOfNetworkFallbackToTypeNoneIfNotConnected(NetworkInfo info LOG.d(LOG_TAG, "Connection Type: " + type); return type; } + private String getTypeOfNetworkFallbackToTypeNoneIfNotConnected(NetworkInfo info) { + // the info might still be null in this part of the code + return this.getTypeOfNetworkFallbackToTypeNoneIfNotConnected(info, false); + } /** * Create a new plugin result and send it back to JavaScript @@ -259,7 +287,7 @@ private void sendUpdate(String type) { * Determine the type of connection * * @param info the network info so we can determine connection type. - * @return the type of mobile network we are on + * @return the type of network we are on */ private String getType(NetworkInfo info) { String type = info.getTypeName().toLowerCase(Locale.US); @@ -270,28 +298,102 @@ private String getType(NetworkInfo info) { } else if (type.toLowerCase().equals(TYPE_ETHERNET) || type.toLowerCase().startsWith(TYPE_ETHERNET_SHORT)) { return TYPE_ETHERNET; } else if (type.equals(MOBILE) || type.equals(CELLULAR)) { - type = info.getSubtypeName().toLowerCase(Locale.US); - if (type.equals(GSM) || - type.equals(GPRS) || - type.equals(EDGE) || - type.equals(TWO_G)) { - return TYPE_2G; - } else if (type.startsWith(CDMA) || - type.equals(UMTS) || - type.equals(ONEXRTT) || - type.equals(EHRPD) || - type.equals(HSUPA) || - type.equals(HSDPA) || - type.equals(HSPA) || - type.equals(THREE_G)) { - return TYPE_3G; - } else if (type.equals(LTE) || - type.equals(UMB) || - type.equals(HSPA_PLUS) || - type.equals(FOUR_G)) { - return TYPE_4G; + return getMobileType(info); + } + return TYPE_UNKNOWN; + } + + /** + * Determine the subtype of mobile connection + * + * @param info the network info so we can determine connection type. + * @return the type of mobile network we are on + */ + private String getMobileType(NetworkInfo info){ + int subTypeId = info.getSubtype(); + String subTypeName = info.getSubtypeName().toLowerCase(Locale.US); + if(is2G(subTypeId, subTypeName)){ + return TYPE_2G; + } else if(is3G(subTypeId, subTypeName)) { + return TYPE_3G; + } else if(is4G(subTypeId, subTypeName)) { + if(isNrAvailable){ // if is LTE network could be 5g if NR is available + return TYPE_5G; } + return TYPE_4G; + } else if(is5G(subTypeId, subTypeName)) { + return TYPE_5G; } return TYPE_UNKNOWN; } + + private boolean is2G(int type, String name){ + return type == TelephonyManager.NETWORK_TYPE_GPRS || + type == TelephonyManager.NETWORK_TYPE_EDGE || + type == TelephonyManager.NETWORK_TYPE_CDMA || + type == TelephonyManager.NETWORK_TYPE_1xRTT || + type == TelephonyManager.NETWORK_TYPE_IDEN || // api< 8: replace by 11 + type == TelephonyManager.NETWORK_TYPE_GSM || // api<25: replace by 16 + name.equals(GSM) || + name.equals(GPRS) || + name.equals(EDGE) || + name.equals(TWO_G); + } + + private boolean is3G(int type, String name){ + return type == TelephonyManager.NETWORK_TYPE_UMTS || + type == TelephonyManager.NETWORK_TYPE_EVDO_0 || + type == TelephonyManager.NETWORK_TYPE_EVDO_A || + type == TelephonyManager.NETWORK_TYPE_HSDPA || + type == TelephonyManager.NETWORK_TYPE_HSUPA || + type == TelephonyManager.NETWORK_TYPE_HSPA || + type == TelephonyManager.NETWORK_TYPE_EVDO_B || // api< 9: replace by 12 + type == TelephonyManager.NETWORK_TYPE_EHRPD || // api<11: replace by 14 + type == TelephonyManager.NETWORK_TYPE_HSPAP || // api<13: replace by 15 + type == TelephonyManager.NETWORK_TYPE_TD_SCDMA || // api<25: replace by 17 + name.startsWith(CDMA) || + name.equals(UMTS) || + name.equals(ONEXRTT) || + name.equals(EHRPD) || + name.equals(HSUPA) || + name.equals(HSDPA) || + name.equals(HSPA) || + name.equals(THREE_G); + } + + private boolean is4G(int type, String name){ + return type == TelephonyManager.NETWORK_TYPE_LTE && name.equals(FOUR_G) || // api<11: replace by 13 + type == TelephonyManager.NETWORK_TYPE_IWLAN || // api<25: replace by 18 + type == NETWORK_TYPE_LTE_CA || // LTE_CA + name.equals(LTE) || + name.equals(UMB) || + name.equals(HSPA_PLUS) || + name.equals(FOUR_G); + } + + private boolean is5G(int type, String name){ + return type == TelephonyManager.NETWORK_TYPE_LTE && name.equals(FIVE_G) || // api<11: replace by 13 + type == NETWORK_TYPE_NR || // api<25: replace by 18 + name.equals(FIVE_G) || + name.equals(NR); + } + + private PhoneStateListener phoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + NetworkManager.this.isNrAvailable = isNrAvailable(serviceState); + updateConnectionInfo(sockMan.getActiveNetworkInfo(), true); + } + }; + + /** + * Determine if NR network is available, for detect sdk < 30 + * + * @param serviceState the ServiceState from PhoneStateListener + * @return flag boolean if NR is available + */ + private boolean isNrAvailable(ServiceState serviceState ){ + String stateStr = serviceState.toString(); + return stateStr.contains("nrState=CONNECTED") || stateStr.contains("isNrAvailable = true"); + } } diff --git a/src/ios/CDVConnection.m b/src/ios/CDVConnection.m index 40b2e42..f011533 100644 --- a/src/ios/CDVConnection.m +++ b/src/ios/CDVConnection.m @@ -60,28 +60,38 @@ - (NSString*)w3cConnectionTypeFor:(CDVReachability*)reachability } else { if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending) { CTTelephonyNetworkInfo *telephonyInfo = [CTTelephonyNetworkInfo new]; - if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) { + NSString *currentRadioAccessTechnology = radioAccessNameIn(telephonyInfo); + if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) { return @"2g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge]) { return @"2g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) { return @"3g"; - } else if ([telephonyInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) { + } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) { return @"4g"; + } + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_1 + else if (@available(iOS 14.1, *)) { + if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyNRNSA] || + [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyNR]) { + return @"5g"; + } + } + #endif } } return @"cellular"; @@ -98,15 +108,15 @@ - (NSString*)w3cConnectionTypeFor:(CDVReachability*)reachability } default: return @"unknown"; - } } - (BOOL)isCellularConnection:(NSString*)theConnectionType { return [theConnectionType isEqualToString:@"2g"] || - [theConnectionType isEqualToString:@"3g"] || - [theConnectionType isEqualToString:@"4g"] || - [theConnectionType isEqualToString:@"cellular"]; + [theConnectionType isEqualToString:@"3g"] || + [theConnectionType isEqualToString:@"4g"] || + [theConnectionType isEqualToString:@"5g"] || + [theConnectionType isEqualToString:@"cellular"]; } - (void)updateReachability:(CDVReachability*)reachability @@ -159,4 +169,13 @@ - (void)pluginInitialize } } +static NSString *radioAccessNameIn(CTTelephonyNetworkInfo *networkInfo) { + if (@available(iOS 13.0, *)) { + if (networkInfo.currentRadioAccessTechnology == nil && networkInfo.dataServiceIdentifier) { + return [networkInfo.serviceCurrentRadioAccessTechnology objectForKey:networkInfo.dataServiceIdentifier]; + } + } + return networkInfo.currentRadioAccessTechnology; +} + @end diff --git a/tests/tests.js b/tests/tests.js index 30e590a..a1fb507 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -37,6 +37,7 @@ exports.defineAutoTests = function () { cellular: 1, '3g': 1, '4g': 1, + '5g': 1, none: 1 }; expect(validValues[navigator.connection.type]).toBe(1); @@ -49,6 +50,7 @@ exports.defineAutoTests = function () { expect(Connection.CELL_2G).toBe('2g'); expect(Connection.CELL_3G).toBe('3g'); expect(Connection.CELL_4G).toBe('4g'); + expect(Connection.CELL_5G).toBe('5g'); expect(Connection.NONE).toBe('none'); expect(Connection.CELL).toBe('cellular'); }); diff --git a/types/index.d.ts b/types/index.d.ts index 60b6c26..4fee538 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -42,6 +42,7 @@ interface Connection { * Connection.CELL_2G * Connection.CELL_3G * Connection.CELL_4G + * Connection.CELL_5G * Connection.CELL * Connection.NONE */ @@ -57,6 +58,7 @@ declare var Connection: { CELL_2G: string; CELL_3G: string; CELL_4G: string; + CELL_5G: string; CELL: string; NONE: string; -} \ No newline at end of file +} diff --git a/www/Connection.js b/www/Connection.js index fb1d0ad..962908e 100644 --- a/www/Connection.js +++ b/www/Connection.js @@ -29,6 +29,7 @@ module.exports = { CELL_2G: '2g', CELL_3G: '3g', CELL_4G: '4g', + CELL_5G: '5g', CELL: 'cellular', NONE: 'none' };