/* * This source is part of the * _____ ___ ____ * __ / / _ \/ _ | / __/___ _______ _ * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / * /___/ * repository. * * Copyright (C) 2013-2016 Carmen Alvarez (c@rmen.ca) * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ca.rmen.android.networkmonitor.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import android.Manifest; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.support.v4.app.ActivityCompat; import android.telephony.CellInfo; import android.telephony.CellInfoLte; import android.telephony.CellSignalStrength; import android.telephony.CellSignalStrengthLte; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import ca.rmen.android.networkmonitor.Constants; /** * The logic in this class comes from the Android source code. It is copied here because some of this logic is available only on API level 17+. */ public class NetMonSignalStrength { private static final String TAG = Constants.TAG + NetMonSignalStrength.class.getSimpleName(); public static final int UNKNOWN = Integer.MAX_VALUE; public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; //Use int max, as -1 is a valid value in signal strength private static final int INVALID = 0x7FFFFFFF; private static final int SIGNAL_STRENGTH_POOR = 1; private static final int SIGNAL_STRENGTH_MODERATE = 2; private static final int SIGNAL_STRENGTH_GOOD = 3; private static final int SIGNAL_STRENGTH_GREAT = 4; private static final int GSM_SIGNAL_STRENGTH_GREAT = 12; private static final int GSM_SIGNAL_STRENGTH_GOOD = 8; private static final int GSM_SIGNAL_STRENGTH_MODERATE = 5; private final TelephonyManager mTelephonyManager; private final Context mContext; public NetMonSignalStrength(Context context) { mContext = context; mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); } /** * @return a value between 0 {@link #SIGNAL_STRENGTH_NONE_OR_UNKNOWN} and 4 {@link #SIGNAL_STRENGTH_GREAT}. */ public int getLevel(SignalStrength signalStrength) { Log.v(TAG, "getLevel " + signalStrength); if (signalStrength.isGsm()) { return getGSMSignalStrength(signalStrength); } else { return getCDMASignalStrength(signalStrength); } } /** * @return the signal strength as dBm. */ public int getDbm(SignalStrength signalStrength) { Log.v(TAG, "getDbm " + signalStrength); int dBm; if (signalStrength.isGsm()) { if (getLteLevel(signalStrength) == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { dBm = getGsmDbm(signalStrength); } else { dBm = getLteDbm(signalStrength); } } else { int cdmaDbm = signalStrength.getCdmaDbm(); int evdoDbm = signalStrength.getEvdoDbm(); return evdoDbm == -120 ? cdmaDbm : cdmaDbm == -120 ? evdoDbm : cdmaDbm < evdoDbm ? cdmaDbm : evdoDbm; } return dBm; } private int getCDMASignalStrength(SignalStrength signalStrength) { Log.v(TAG, "getCDMASignalStrength " + signalStrength); int level; int cdmaLevel = getCdmaLevel(signalStrength); int evdoLevel = getEvdoLevel(signalStrength); if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { /* We don't know evdo, use cdma */ level = getCdmaLevel(signalStrength); } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { /* We don't know cdma, use evdo */ level = getEvdoLevel(signalStrength); } else { /* We know both, use the lowest level */ level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel; } Log.v(TAG, "getLevel=" + level); return level; } /** * Get cdma as level 0..4 */ private int getCdmaLevel(SignalStrength signalStrength) { final int cdmaDbm = signalStrength.getCdmaDbm(); final int cdmaEcio = signalStrength.getCdmaEcio(); int levelDbm; int levelEcio; if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT; else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD; else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE; else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR; else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // Ec/Io are in dB*10 if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT; else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD; else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE; else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR; else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; int level = levelDbm < levelEcio ? levelDbm : levelEcio; Log.v(TAG, "getCdmaLevel=" + level); return level; } /** * Get Evdo as level 0..4 */ private int getEvdoLevel(SignalStrength signalStrength) { int evdoDbm = signalStrength.getEvdoDbm(); int evdoSnr = signalStrength.getEvdoSnr(); int levelEvdoDbm; int levelEvdoSnr; if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT; else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD; else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE; else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR; else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT; else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD; else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE; else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR; else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; int level = levelEvdoDbm < levelEvdoSnr ? levelEvdoDbm : levelEvdoSnr; Log.v(TAG, "getEvdoLevel=" + level); return level; } /** * Get signal level as an int from 0..4 */ private int getGSMSignalStrength(SignalStrength signalStrength) { Log.v(TAG, "getGSMSignalStrength " + signalStrength); // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 // asu = 0 (-113dB or less) is very weak // signal, its better to show 0 bars to the user in such cases. // asu = 99 is a special case, where the signal strength is unknown. int asu = signalStrength.getGsmSignalStrength(); if (asu > 0) { if (asu <= 2 || asu == 99) return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; else if (asu >= GSM_SIGNAL_STRENGTH_GREAT) return SIGNAL_STRENGTH_GREAT; else if (asu >= GSM_SIGNAL_STRENGTH_GOOD) return SIGNAL_STRENGTH_GOOD; else if (asu >= GSM_SIGNAL_STRENGTH_MODERATE) return SIGNAL_STRENGTH_MODERATE; else return SIGNAL_STRENGTH_POOR; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // On a Huawei P9 Lite, getGsmSignalStrength() always returns 0, but the private // apis getGsmLevel() and getLteLevel() don't. Let's try them. try { if (mTelephonyManager.getNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) { Method methodGetLteLevel = SignalStrength.class.getMethod("getLteLevel"); return (Integer) methodGetLteLevel.invoke(signalStrength); } else { Method methodGetGsmLevel = SignalStrength.class.getMethod("getGsmLevel"); return (Integer) methodGetGsmLevel.invoke(signalStrength); } } catch (Throwable t) { Log.v(TAG, "getGsmLevel or getLteLevel failed: " + t.getMessage(), t); return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } else { return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } /** * Get the GSM signal strength as dBm */ private int getGsmDbm(SignalStrength signalStrength) { Log.v(TAG, "getGsmDbm" + signalStrength); int level = signalStrength.getGsmSignalStrength(); int asu = level == 99 ? SIGNAL_STRENGTH_NONE_OR_UNKNOWN : level; if (asu != SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { return -113 + 2 * asu; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // On the Huawei P9 Lite, getGsmSignalStrength() returns 0 but getGsmDbm() returns a value. try { Method methodGetGsmDbm = SignalStrength.class.getMethod("getGsmDbm"); return (Integer) methodGetGsmDbm.invoke(signalStrength); } catch (Throwable t) { Log.v(TAG, "Couldn't execute getGsmDbm() " + t.getMessage(), t); return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } else { return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } /** * Get LTE as level 0..4 */ private int getLteLevel(SignalStrength signalStrength) { Log.v(TAG, "getLteLevel " + signalStrength); // For now there's no other way besides reflection :( The getLteLevel() method // in the SignalStrength class access private fields. // On some Samsung devices, getLteLevel() can actually return 4 (the highest signal strength) even if we're not on Lte. // It seems that Samsung has reimplemented getLteLevel(). So we add an extra check to make sure we only use Lte level if we're on LTE. if (mTelephonyManager.getNetworkType() != TelephonyManager.NETWORK_TYPE_LTE) { Log.v(TAG, "getLteLevel: returning " + SIGNAL_STRENGTH_NONE_OR_UNKNOWN + " because we're not on Lte"); return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } try { Method methodGetLteLevel = SignalStrength.class.getMethod("getLteLevel"); return (Integer) methodGetLteLevel.invoke(signalStrength); } catch (Throwable t) { Log.v(TAG, "getLteLevel failed: " + t.getMessage(), t); return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } /** * Get LTE as dBm */ private int getLteDbm(SignalStrength signalStrength) { Log.v(TAG, "getLteDbm " + signalStrength); // For now there's no other way besides reflection :( The getLteDbm() method // in the SignalStrength class returns a private field which is not // accessible in any public, non-hidden methods. try { Method methodGetLteDbm = SignalStrength.class.getMethod("getLteDbm"); return (Integer) methodGetLteDbm.invoke(signalStrength); } catch (Throwable t) { Log.v(TAG, "getLteDbm failed: " + t.getMessage(), t); return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } /** * Get the signal level as an asu value between 0..31, 99 is unknown */ public int getAsuLevel(SignalStrength signalStrength) { int asuLevel; if (signalStrength.isGsm()) { if (getLteLevel(signalStrength) == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { asuLevel = signalStrength.getGsmSignalStrength(); } else { asuLevel = getLteAsuLevel(signalStrength); } } else { int cdmaAsuLevel = getCdmaAsuLevel(signalStrength); int evdoAsuLevel = getEvdoAsuLevel(signalStrength); if (evdoAsuLevel == 0) { /* We don't know evdo use, cdma */ asuLevel = cdmaAsuLevel; } else if (cdmaAsuLevel == 0) { /* We don't know cdma use, evdo */ asuLevel = evdoAsuLevel; } else { /* We know both, use the lowest level */ asuLevel = cdmaAsuLevel < evdoAsuLevel ? cdmaAsuLevel : evdoAsuLevel; } } Log.v(TAG, "getAsuLevel=" + asuLevel); return asuLevel; } @TargetApi(17) public int getLteRsrq(SignalStrength signalStrength) { // Two hacky ways to attempt to get the rsrq // First hacky way: reflection on the signalStrength object try { Method method = SignalStrength.class.getDeclaredMethod("getLteRsrq"); int rsrq = (Integer) method.invoke(signalStrength); Log.v(TAG, "getLteRsrq: found " + rsrq + " using SignalStrength.getLteRsrq()"); if (rsrq < 0) return rsrq; } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Log.e(TAG, "getLteRsrq Could not get rsrq", e); } // Second hacky way: reflection on the CellInfo object. if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo(); if (cellInfos != null) { for (CellInfo cellInfo : cellInfos) { if (cellInfo.isRegistered()) { if (cellInfo instanceof CellInfoLte) { CellSignalStrengthLte signalStrengthLte = ((CellInfoLte) cellInfo).getCellSignalStrength(); try { Field fieldRsrq = CellSignalStrength.class.getDeclaredField("mRsrq"); fieldRsrq.setAccessible(true); int rsrq = (Integer) fieldRsrq.get(signalStrengthLte); Log.v(TAG, "getLteRsrq: found " + rsrq + " using CellInfoLte.mRsrq"); if (rsrq < 0) return rsrq; } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { Log.e(TAG, "getRsrq Could not get Rsrq", e); } } } } } } return SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } /** * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 */ private int getLteAsuLevel(SignalStrength signalStrength) { int lteAsuLevel; int lteDbm = getLteDbm(signalStrength); /* * 3GPP 27.007 (Ver 10.3.0) Sec 8.69 * 0 -140 dBm or less * 1 -139 dBm * 2...96 -138... -44 dBm * 97 -43 dBm or greater * 255 not known or not detectable */ /* * validateInput will always give a valid range between -140 t0 -44 as * per ril.h. so RSRP >= -43 & <-140 will fall under asu level 255 * and not 97 or 0 */ if (lteDbm == INVALID) lteAsuLevel = 255; else lteAsuLevel = lteDbm + 140; Log.v(TAG, "Lte Asu level: " + lteAsuLevel); return lteAsuLevel; } /** * Get the cdma signal level as an asu value between 0..31, 99 is unknown */ private int getCdmaAsuLevel(SignalStrength signalStrength) { final int cdmaDbm = signalStrength.getCdmaDbm(); final int cdmaEcio = signalStrength.getCdmaEcio(); int cdmaAsuLevel; int ecioAsuLevel; if (cdmaDbm >= -75) cdmaAsuLevel = 16; else if (cdmaDbm >= -82) cdmaAsuLevel = 8; else if (cdmaDbm >= -90) cdmaAsuLevel = 4; else if (cdmaDbm >= -95) cdmaAsuLevel = 2; else if (cdmaDbm >= -100) cdmaAsuLevel = 1; else cdmaAsuLevel = 99; // Ec/Io are in dB*10 if (cdmaEcio >= -90) ecioAsuLevel = 16; else if (cdmaEcio >= -100) ecioAsuLevel = 8; else if (cdmaEcio >= -115) ecioAsuLevel = 4; else if (cdmaEcio >= -130) ecioAsuLevel = 2; else if (cdmaEcio >= -150) ecioAsuLevel = 1; else ecioAsuLevel = 99; int level = cdmaAsuLevel < ecioAsuLevel ? cdmaAsuLevel : ecioAsuLevel; Log.v(TAG, "getCdmaAsuLevel=" + level); return level; } /** * Get the evdo signal level as an asu value between 0..31, 99 is unknown */ private int getEvdoAsuLevel(SignalStrength signalStrength) { int evdoDbm = signalStrength.getEvdoDbm(); int evdoSnr = signalStrength.getEvdoSnr(); int levelEvdoDbm; int levelEvdoSnr; if (evdoDbm >= -65) levelEvdoDbm = 16; else if (evdoDbm >= -75) levelEvdoDbm = 8; else if (evdoDbm >= -85) levelEvdoDbm = 4; else if (evdoDbm >= -95) levelEvdoDbm = 2; else if (evdoDbm >= -105) levelEvdoDbm = 1; else levelEvdoDbm = 99; if (evdoSnr >= 7) levelEvdoSnr = 16; else if (evdoSnr >= 6) levelEvdoSnr = 8; else if (evdoSnr >= 5) levelEvdoSnr = 4; else if (evdoSnr >= 3) levelEvdoSnr = 2; else if (evdoSnr >= 1) levelEvdoSnr = 1; else levelEvdoSnr = 99; int level = levelEvdoDbm < levelEvdoSnr ? levelEvdoDbm : levelEvdoSnr; Log.v(TAG, "getEvdoAsuLevel=" + level); return level; } }