onCellInfoChanged() callback is always null - android

i am trying to get a list of all available cells the device can find. But i am stuck, as my CellInfo is always null and i don't figure why. Can someone give me a hint? There is pretty few info on onCellInfoChanged() at google.
MainActivity:
CellListener cellListener = new CellListener(this);
cellListener.start();
CellListener:
public class CellListener extends PhoneStateListener {
private static final String TAG = "CellListener";
private TelephonyManager telephonyManager = null;
private PhoneStateListener listener = null;
private String newCell = null;
private int events = PhoneStateListener.LISTEN_CELL_LOCATION | PhoneStateListener.LISTEN_CELL_INFO;
private Context context = null;
public CellListener(Context context) {
this.context = context;
}
public void start() {
telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
CellLocation.requestLocationUpdate();
telephonyManager.listen(this, events);
}
#Override
public void onCellInfoChanged(List<CellInfo> cellInfo) {
Log.i("CellListener","onCellInfoChanged(List<CellInfo> cellInfo) ");
super.onCellInfoChanged(cellInfo);
if(cellInfo == null) return; // this always null here
for (CellInfo c : cellInfo) {
Log.i("CellListener"," c = "+c);
}
}
#Override
public void onCellLocationChanged(CellLocation location) {
if (!(location instanceof GsmCellLocation)) {
return;
}
GsmCellLocation gsmCell = (GsmCellLocation) location;
String operator = telephonyManager.getNetworkOperator();
if (operator == null || operator.length() < 4) {
return;
}
newCell = operator.substring(0, 3) + ':' + operator.substring(3) + ':'
+ gsmCell.getLac() + ':' + gsmCell.getCid();
Log.i(TAG,"newCell = "+newCell);
}
}
Logcat:
11-18 14:50:23.806: I/CellListener(4953): newCell = 262:02:4311:99031735
11-18 14:50:23.814: I/CellListener(4953): onCellInfoChanged(List<CellInfo> cellInfo)
As you can see both events (onCellInfoChanged & onCellLocationChanged) get triggered once and the latter is correctly returning the current cell the device is using.

The real reason you are only being called exactly once and with null as an argument is the following: there appears to be a rate-limiting setting in place by default, as can be observed in the "testing" app, which can be accessed by dialing *#*#INFO#*#* (i.e. *#*#4636#*#*).
In the testing app, choose "Phone Information" and scroll down to the button CELLINFOLISTRATE xxxx, in your case presumably CELLINFOLISTRATE 2147483647. As 2147483647 == MAX_INT, this probably means no calls at all
For me (stock android 6.0, nexus 6), there's the a choice between MAX_INT (one call with null), 0 and 1000.
I'm not 100% sure what these values mean, but presumably the 0 stands for instant (and thus very many) calls, and 1000 for something like at least a second between calls. Keep in mind though, this is pure speculation.
I will edit this answer as I find out more, for example by looking at the implementation of said testing app.

#bofredo: It is returning null because you haven't defined CellInfo yet.
Can't see from your question if you're using CDMA, GSM, or LTE. From memory, CDMA doesn't seem to return anything, but GSM (CellInfoGsm) and LTE (CellInfoLte) do. So doing something like for example:
CellInfoGsm cellInfoGsm = (CellInfoGsm) cellInfo;
CellInfoLte cellInfoLte = (CellInfoLte) cellInfo;
will return an instance of CellInfoGsm or CellInfoLte which will then allow you to retrieve more information about a cell like:
CellIdentityGsm cellIdentityGsm = cellInfoGsm.getCellIdentity();
CellIdentityLte cellIdentityLte = cellInfoLte.getCellIdentity();
or
CellSignalStrengthGsm cellSignalStrengthGsm = cellInfoGsm.getCellSignalStrength();
CellSignalStrengthLte cellSignalStrengthLte = cellInfoLte.getCellSignalStrength();
Then use cellIdentityGsm, cellIdentityLte, cellSignalStrengthGsm and cellSignalStrengthLte to do what you want in onCellInfoChanged.

In addition to #bimmlerd answer. If you are updating the Phone Information via dialing *#*#INFO#*#* (i.e. *#*#4636#*#*). There are chances that the hidden menu doesn't appear. In that case, use default dialer if you are using some 3rd party call management application (Worked for me as it was not showing any hidden menu for phone information).
https://www.reddit.com/r/GooglePixel/comments/6xc40q/4636_service_menu_not_available_in_oreo/
Note:
The following picture will help you with the latest Android OS. Just update the mobile info refresh rate in phone information 1/2 option (I hope multiple phone information because of multiple sim cards)

Related

Youtube usage calculation using TrafficStats

Using TrafficStats i was checking the youtube app data usage.In some devices it is working fine but not with many other devices.
I found that from developer site, These statistics may not be available on all platforms. If the statistics are not supported by this device, UNSUPPORTED will be returned.
So in these case how can I get the device app usage ?
I was using
TrafficStats.getUidRxBytes(packageInfo.uid) + TrafficStats.getUidTxBytes(packageInfo.uid);
this is returning -1 everytime.
We can use NetworkStats.
https://developer.android.com/reference/android/app/usage/NetworkStats.html
Please see a sample repo which I got the clue.
https://github.com/RobertZagorski/NetworkStats
We can see a similar stackoverflow question as well.
Getting mobile data usage history using NetworkStatsManager
Then I needed to modify this logic for some particular devices. In these devices the normal method won't return proper usage values. So I modified is as
/*
getting youtube usage for both mobile and wifi.
*/
public long getYoutubeTotalusage(Context context) {
String subId = getSubscriberId(context, ConnectivityManager.TYPE_MOBILE);
//both mobile and wifi usage is calculating. For mobile usage we need subscriberid. For wifi we can give it as empty string value.
return getYoutubeUsage(ConnectivityManager.TYPE_MOBILE, subId) + getYoutubeUsage(ConnectivityManager.TYPE_WIFI, "");
}
private long getYoutubeUsage(int networkType, String subScriberId) {
NetworkStats networkStatsByApp;
long currentYoutubeUsage = 0L;
try {
networkStatsByApp = networkStatsManager.querySummary(networkType, subScriberId, 0, System.currentTimeMillis());
do {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
networkStatsByApp.getNextBucket(bucket);
if (bucket.getUid() == packageUid) {
//rajeesh : in some devices this is immediately looping twice and the second iteration is returning correct value. So result returning is moved to the end.
currentYoutubeUsage = (bucket.getRxBytes() + bucket.getTxBytes());
}
} while (networkStatsByApp.hasNextBucket());
} catch (RemoteException e) {
e.printStackTrace();
}
return currentYoutubeUsage;
}
private String getSubscriberId(Context context, int networkType) {
if (ConnectivityManager.TYPE_MOBILE == networkType) {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
return tm.getSubscriberId();
}
return "";
}

Reliable method to check if device can send SMS with current connectivity

I have an application that send SMS automatically to a remote server. I want to check if the phone is connected to a network capable to send SMS before calling the SMSManager.send() method.
I tried the following method :
TelephonyManager tlm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
boolean hasNetwork = tlm.getNetworkType() != android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
Unfortunately, this method works on some phones like Acer or Nexus but not on others like some Sony Xperia.
I then tried an other approach :check if the phone is connected to antennas (and I assume that there is network when connected to an antenna)
private static boolean isSmsNetworkAvailable(Context context) {
TelephonyManager tlm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
boolean hasNetwork = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// new API
List<CellInfo> listCellInfo = tlm.getAllCellInfo();
if (listCellInfo != null && !listCellInfo.isEmpty()) {
for (CellInfo cellInfo : listCellInfo) {
if (cellInfo.isRegistered()) {
hasNetwork = true;
break;
}
}
}
} else {
// old API
List<NeighboringCellInfo> listNeighboringCellInfo = tlm.getNeighboringCellInfo();
if (listNeighboringCellInfo != null && !listNeighboringCellInfo.isEmpty()) {
hasNetwork = true;
}
}
return hasNetwork;
}
This method is also unreliable , it works on Nexus, Xperia, but not on Acer phones (for example)...
So the question is : Is there a reliable method to check if a phone is really capable to send SMS with the current connectivity?

CellIdentityLte getCi() returning -1 in android

I have a method which returns cell id for LTE device, but on some devices it is returning -1. Here is the method:
public int getCellId() {
int cellId = Integer.MAX_VALUE;
CellInfo cellInfo = null;
List<CellInfo> allCellInfo = telephonyManager.getAllCellInfo();
if(allCellInfo!= null && allCellInfo.size()>0)
cellInfo = allCellInfo.get(0);
if (cellInfo instanceof CellInfoLte) {
CellInfoLte cellInfoLte = (CellInfoLte) cellInfo;
if (cellInfoLte != null) {
cellId = cellInfoLte.getCellIdentity().getCi();
}
}
return cellId;
}
You have it implemented correctly, unfortunately not all manufacturers implement all the Android APIs correctly, especially around the telephony area. Sometimes all the LTE values will be -1 also, sometimes just the CI.

get SignalStrength without Using PhoneStateListener onSignalStrengthchanged

does anyone know how to get the signal strength without having to call the onSignalStrengthChanged. The problem with onSignalStrengthchanged is that is it called when the signal strength changes and I need to get the value of signalstrength according to a different criteria.
Thanks in advance.
On API level 17 only, here's some code that can be used in an Activity (or any other Context child class):
import android.telephony.CellInfo;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.CellSignalStrengthGsm;
import android.telephony.CellSignalStrengthLte;
import android.telephony.TelephonyManager;
try {
final TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
for (final CellInfo info : tm.getAllCellInfo()) {
if (info instanceof CellInfoGsm) {
final CellSignalStrengthGsm gsm = ((CellInfoGsm) info).getCellSignalStrength();
// do what you need
} else if (info instanceof CellInfoCdma) {
final CellSignalStrengthCdma cdma = ((CellInfoCdma) info).getCellSignalStrength();
// do what you need
} else if (info instanceof CellInfoLte) {
final CellSignalStrengthLte lte = ((CellInfoLte) info).getCellSignalStrength();
// do what you need
} else {
throw new Exception("Unknown type of cell signal!");
}
}
} catch (Exception e) {
Log.e(TAG, "Unable to obtain cell signal information", e);
}
Previous versions of Android require you to call the listener, there is no other alternative (see this link).
Also ensure that your application contains the appropriate permissions.
you can access SignalStrength via reflection call. Please go through the link for implementation http://blog.ajhodges.com/2013/03/reading-lte-signal-strength-rssi-in.html
There is another API in Android called CellInfo. But I am not sure whether signal strength returned by OnSignalStrengthsChanged() and CellInfo is same or not.
https://developer.android.com/reference/android/telephony/CellSignalStrength.html
Based on Andre's answer above, in Kotlin you can use this one-liner (again API 17+):
fun getRadioSignalLevel(): Int {
return when (val info = telephonyManager.allCellInfo?.firstOrNull()) {
is CellInfoLte -> info.cellSignalStrength.level
is CellInfoGsm -> info.cellSignalStrength.level
is CellInfoCdma -> info.cellSignalStrength.level
is CellInfoWcdma -> info.cellSignalStrength.level
else -> 0
}
}

How to find out whether android device has cellular radio module?

How can I find out for sure that device really has gsm, cdma or other cellular network equipment (not just WiFi)?
I don't want to check current connected network state, because device can be offline in the moment.
And I don't want to check device id via ((TelephonyManager) act.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId() because some devices would just give you polymorphic or dummy device ID.
Actualy, I need to check cell equipment exactly for skipping TelephonyManager.getDeviceId and performing Settings.Secure.ANDROID_ID check on those devices that don't have cellular radio. I have at least one tablet (Storage Options Scroll Excel 7") which returns different IMEIs every time you ask it, although it should return null as it has no cell radio (the same situation here: Android: getDeviceId() returns an IMEI, adb shell dumpsys iphonesubinfo returns Device ID=NULL). But I need to have reliable device id that is the same every time I ask.
I'd be glad to hear your thoughts!
If you're publishing in the store, and you want to limit your application only being visible to actual phones, you could add a <uses-feature> into your manifest that asks for android.hardware.telephony. Check out if that works for you from the documentation.
Just in case somebody needs complete solution for this:
Reflection is used because some things may not exist on some firmware versions.
MainContext - main activity context.
static public int getSDKVersion()
{
Class<?> build_versionClass = null;
try
{
build_versionClass = android.os.Build.VERSION.class;
}
catch (Exception e)
{
}
int retval = -1;
try
{
retval = (Integer) build_versionClass.getField("SDK_INT").get(build_versionClass);
}
catch (Exception e)
{
}
if (retval == -1)
retval = 3; //default 1.5
return retval;
}
static public boolean hasTelephony()
{
TelephonyManager tm = (TelephonyManager) Hub.MainContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null)
return false;
//devices below are phones only
if (Utils.getSDKVersion() < 5)
return true;
PackageManager pm = MainContext.getPackageManager();
if (pm == null)
return false;
boolean retval = false;
try
{
Class<?> [] parameters = new Class[1];
parameters[0] = String.class;
Method method = pm.getClass().getMethod("hasSystemFeature", parameters);
Object [] parm = new Object[1];
parm[0] = "android.hardware.telephony";
Object retValue = method.invoke(pm, parm);
if (retValue instanceof Boolean)
retval = ((Boolean) retValue).booleanValue();
else
retval = false;
}
catch (Exception e)
{
retval = false;
}
return retval;
}

Categories

Resources