christianrowlands / android-network-survey

Cellular Survey Android App
https://www.networksurvey.app
Apache License 2.0
140 stars 27 forks source link

Override Network Showing LTE-CA, when isUsingCarrierAggregation is false #47

Open pt-at-tg opened 1 month ago

pt-at-tg commented 1 month ago

Hi Christian, love the app - how are you making the calculation for Override Network, specifically for LTE-CA. I've got an (internal) app for Teragence which exposes the NetworkRegistrationInfo.DataSpecificInfo and "isUsingCarrierAggregation" is "false", but on NetworkSurvey the Override Network is LTE-CA.

(And btw, totally agree with your info message about Override Network...total scam!)

christianrowlands commented 1 month ago

I get the Override Network from the TelephonyDisplayInfo callback. Here is the class I register for the callback:

    @RequiresApi(api = Build.VERSION_CODES.S)
    private static class OverrideNetworkTypeListener extends TelephonyCallback implements TelephonyCallback.DisplayInfoListener
    {
        int overrideNetworkType = -1;

        @Override
        public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo)
        {
            overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
        }
    }

And of course to register it:

getTelephonyManager().registerTelephonyCallback(executorService, displayInfoListener);

As far as when the service provider decides to tell the phone it is LTE-CA for the override network, I am not sure.

I should add a spot in the NS display where it shows the result of isUsingCarrierAggregation. That would be helpful.

christianrowlands commented 1 month ago

Well this is annoying. I went to go use the NetworkRegistrationInfo#isUsingCarrierAggregation method in my app, but it looks like it is not exposed to apps. I can call NetworkRegistrationInfo#toString and see the value of it, but I can't call the method.

I have parsed the toString result before, so I will probably just do that again. But that is less than ideal.

pt-at-tg commented 1 month ago

Yep, that's what I had to do - here's the (kotlin) code if it helps

@RequiresApi(Build.VERSION_CODES.R)
fun collectNetworkRegistrationInfo(context: Context): NetworkRegistrationData? {
    val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
    if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        return null
    }

    telephonyManager.serviceState?.networkRegistrationInfoList?.forEach { regInfo ->
        val regInfoString = regInfo.toString()
        val dataSpecificString = getStringBetweenStrings(regInfoString, "DataSpecificRegistrationInfo { ", "}")
            ?: getStringBetweenStrings(regInfoString, "DataSpecificRegistrationInfo :{ ", "}")

        val nrStateString = getStringBetweenStrings(regInfoString, "nrState=", " ")
            ?: "UNKNOWN"  // Fallback to "UNKNOWN" if parsing fails or `nrState=` is not followed by a space

        dataSpecificString?.let {
            try {
                val dataMap = parseDataSpecificString(it)
                val nrState = nrStateString
                if (dataMap.isNotEmpty()) {
                    return NetworkRegistrationData(
                        domain = regInfo.domain.toString(),
                        transportType = regInfo.transportType.toString(),
                        accessNetworkTechnology = regInfo.accessNetworkTechnology.toString(),
                        availableServices = regInfo.availableServices.map { it.toString() },
                        dataSpecificInfo = DataSpecificRegistrationInfo(
                            maxDataCalls = dataMap["maxDataCalls"]?.toIntOrNull() ?: 0,
                            isDcNrRestricted = dataMap["isDcNrRestricted"]?.toBoolean() ?: false,
                            isNrAvailable = dataMap["isNrAvailable"]?.toBoolean() ?: false,
                            isEnDcAvailable = dataMap["isEnDcAvailable"]?.toBoolean() ?: false,
                            lteVopsSupport = dataMap["lteVopsSupport"]?.toIntOrNull() ?: 0,
                            emcBearerSupport = dataMap["emcBearerSupport"]?.toIntOrNull() ?: 0,
                            isUsingCarrierAggregation = dataMap["isUsingCarrierAggregation"]?.toBoolean() ?: false
                        ),
                        nrState = nrState
                    )
                }
            } catch (e: Exception) {
                Log.e("TgSdkMeasurementManager", "Error parsing DataSpecificInfo: ${e.message}")
            }
        }
    }

    return null
}

private fun getStringBetweenStrings(fullString: String, start: String, end: String): String? {
    val startIndex = fullString.indexOf(start) + start.length
    val endIndex = if (end == " ") fullString.indexOf(' ', startIndex) else fullString.indexOf(end, startIndex)
    if (startIndex < start.length || endIndex == -1) return null
    return fullString.substring(startIndex, endIndex).trim()
}

private fun parseDataSpecificString(dataString: String): Map<String, String> {
    return dataString.replace(" = ", "=")
        .split(" ")
        .mapNotNull { it.split("=").takeIf { splitResult -> splitResult.size == 2 } }
        .associate { it[0] to it[1] }
}
christianrowlands commented 1 month ago

@pt-at-tg , Do you ever see isUsingCarrierAggregation set to true? So far I have not seen it, and I ask because I have been pulling the rejectCode from that same toString for a few years now and I have never seen that value be anything except 0. It seems that some values are never set.

pt-at-tg commented 1 month ago

Hmm, I think you're right. Over the July data set where we have access to phone state these are the figures

Total count of records with 'isUsingCarrierAggregation': true -> 0 Total count of records with 'isUsingCarrierAggregation': false -> 7045

My Pixel never goes to 4G+ but I suspect Google don't play those games. I have an Oppo, and I do know an area where I've seen 4G+ on EE in London, so I'll confirm if the value is false there anyway