How to get network usage of apps on Android Q? - android

Background
I know that we can get the network usage (total bandwidth used of mobile&Wifi so far, from some specific time) of a specified app by using something like that (asked in the past, here) :
private final static int[] NETWORKS_TYPES = new int[]{ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_MOBILE};
long rxBytes=0L, txBytes=0L;
final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String subscriberId = telephonyManager.getSubscriberId();
final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
final int uid = applicationInfo.uid;
for (int networkType : NETWORKS_TYPES) {
final NetworkStats networkStats = networkStatsManager.queryDetailsForUid(networkType, subscriberId, 0, System.currentTimeMillis(), uid);
final Bucket bucketOut = new Bucket();
while (true) {
networkStats.getNextBucket(bucketOut);
final long rxBytes = bucketOut.getRxBytes();
if (rxBytes >= 0)
totalRx += rxBytes;
final long txBytes = bucketOut.getTxBytes();
if (txBytes >= 0)
totalTx += txBytes;
if (!networkStats.hasNextBucket())
break;
}
}
}
Docs:
https://developer.android.com/reference/android/app/usage/NetworkStatsManager.html#queryDetailsForUid(int,%20java.lang.String,%20long,%20long,%20int)
It's also possible to get the global network usage (using TrafficStats.getUidRxBytes(applicationInfo.uid) and TrafficStats.getUidTxBytes(applicationInfo.uid) ), but that's not what this thread is all about.
The problem
It seems Android Q is planned to cause a lot of device-identity functions to stop working anymore, and getSubscriberId is one of them.
What I've tried
I tried to set the targetSdk to 29 (Q) and see what happens when I try to use this.
As expected, I got an exception that shows me that I can't do it anymore. It says :
019-06-11 02:08:01.871 13558-13558/com.android.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.myapplication, PID: 13558
java.lang.SecurityException: getSubscriberId: The user 10872 does not meet the requirements to access device identifiers.
at android.os.Parcel.createException(Parcel.java:2069)
at android.os.Parcel.readException(Parcel.java:2037)
at android.os.Parcel.readException(Parcel.java:1986)
at com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getSubscriberIdForSubscriber(IPhoneSubInfo.java:984)
at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3498)
at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3473)
Searching the Internet and here, I don't see this mentioned, but I have found about similar issues, of getting IMEI and other identifiers:
I am getting IMEI null in Android Q?
https://issuetracker.google.com/issues/130202003
https://issuetracker.google.com/issues/129583175
https://developer.android.com/preview/privacy/data-identifiers#device-ids
So for now I just made a bug report about it here (including a sample project) :
https://issuetracker.google.com/issues/134919382
The question
Is it possible to get network usage of a specified app on Android Q (when targeting to it) ? Maybe without subscriberId?
If so, how?
If not, is it possible by having root, or via adb?
EDIT:
OK, I don't know how to officially use this, but at least for root access it is possible to get the subscriberId, using this solution, found from here.
Meaning something like that:
#SuppressLint("MissingPermission", "HardwareIds")
fun getSubscriberId(telephonyManager: TelephonyManager): String? {
try {
return telephonyManager.subscriberId
} catch (e: Exception) {
}
val commandResult = Root.runCommands("service call iphonesubinfo 1 | grep -o \"[0-9a-f]\\{8\\} \" | tail -n+3 | while read a; do echo -n \"\\u\${a:4:4}\\u\${a:0:4}\"; done")
val subscriberId = commandResult?.getOrNull(0)
return if (subscriberId.isNullOrBlank()) null else subscriberId
}
It's not an official solution, of course, but it's better than nothing...
EDIT: the part of getting it via root is wrong. It doesn't help in any way.

You can provide null value for API 29 and above. It returns values ​​for both WIFI and Mobile Data.
Documentation:
If applicable, the subscriber id of the network interface.
Starting with API level 29, the subscriberId is guarded by additional restrictions. Calling apps that do not meet the new requirements to access the subscriberId can provide a null value when querying for the mobile network type to receive usage for all mobile networks. For additional details see TelephonyManager.getSubscriberId().
Permissions (Don't forget to get permission from the user):
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
Example code:
//network stats
NetworkStatsManager networkStatsManager = (NetworkStatsManager) activity.getSystemService(Context.NETWORK_STATS_SERVICE);
int[] networkTypes = new int[]{NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_WIFI};
String subscriberId;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
TelephonyManager telephonyManager = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
try {
subscriberId = telephonyManager.getSubscriberId(); //MissingPermission
} catch (SecurityException e) {
subscriberId = null;
}
} else {
subscriberId = null;
}
Get NetworkStats for an app:
long receivedBytes = 0;
long transmittedBytes = 0;
for (int networkType : networkTypes) {
NetworkStats networkStats;
try {
networkStats = networkStatsManager
.queryDetailsForUid(networkType,
subscriberId,
0,
System.currentTimeMillis(),
appUid);
} catch (SecurityException e) {
networkStats = null;
}
if(networkStats != null) {
NetworkStats.Bucket bucketOut = new NetworkStats.Bucket();
while (networkStats.hasNextBucket()) {
networkStats.getNextBucket(bucketOut);
long rxBytes = bucketOut.getRxBytes();
long txBytes = bucketOut.getTxBytes();
if (rxBytes >= 0) {
receivedBytes += rxBytes;
}
if (txBytes >= 0) {
transmittedBytes += txBytes;
}
}
networkStats.close();
}
}

The google team in the comment of the thread that you have mentioned has said:
" Status: Won't Fix (Intended Behavior)
This is Working As Intended. IMEI is a personal identifier and this is not given out to apps as a matter of policy. There is no workaround.". So I guess the methods in the class NetworkStatsManager which require IMSI (which is also considered as a personal identifier) to work (like the queryDetailsForUid(int, String, long, long, int)) are now broken in Android Q. You may use those methods to get Wifi usage details of the apps (by passing empty string for subscriberId) but for getting Mobile usage details, you now have to rely on the good old TrafficStats class until the issue gets noticed and fixed.

We are using NetworkStatsManager.querySummaryForDevice(). Due to a serendipitous bug, we were passing null as the subscriberId for MOBILE in Q. It appears to be working on our devices. I'm not sure if this is a bug or a feature, but the values match our expected cellular usage.
All the said, we could just use TrafficStats for this use case, but it's erratic before Pie.

Related

Android API 29 Permission Error READ_PRIVILEGED_PHONE_STATE [duplicate]

I am getting the IMEI ID null from the telephonymanager. What to do?
is there any workaround for that?
Android Q has restricted to access for both IMEI and serial no. It is available only for platform and apps with special carrier permission. Also the permission READ_PRIVILEGED_PHONE_STATE is not available for non platform apps.
If you try to access it throws below exception
java.lang.SecurityException: getImeiForSlot: The user 10180 does not meet the requirements to access device identifiers.
Please refer documentation:
https://developer.android.com/preview/privacy/data-identifiers#device-ids
Also refer Issue
I am late to post answer. I still believe my answer will help someone.
Android 10 Restricted developer to Access IMEI number.
You can have a alternate solution by get Software ID. You can use software id as a unique id. Please find below code as i use in Application.
public static String getDeviceId(Context context) {
String deviceId;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
deviceId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID);
} else {
final TelephonyManager mTelephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
deviceId = mTelephony.getDeviceId();
} else {
deviceId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID);
}
}
return deviceId;
}
This just would not work as of Android Q. Third party apps can not use IMEI nor the serial number of a phone and other non-resettable device identifiers.
The only permissions that are able to use those is READ_PRIVILEGED_PHONE_STATE and that cannot be used by any third party apps - Manufacture and Software Applications. If you use that method you will get an error Security exception or get null .
You can still try to get a unique id by using:
import android.provider.Settings.Secure;
private String android_id = Secure.getString(getContext().getContentResolver(),Secure.ANDROID_ID);
The best way to get the IMEI number is as follows:
public static String getIMEIDeviceId(Context context) {
String deviceId;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
} else {
final TelephonyManager mTelephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
return "";
}
}
assert mTelephony != null;
if (mTelephony.getDeviceId() != null)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
deviceId = mTelephony.getImei();
}else {
deviceId = mTelephony.getDeviceId();
}
} else {
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
}
Log.d("deviceId", deviceId);
return deviceId;
}
Just copy the method and use it. It will definitely. However, you might know you can't get IMEI in android Q (version 10). In this code, you can get a unique identifier (alternative id) through any device or any API level.
It works 100%
Thank You!!
And Enjoy Coding :)
As the best practices suggest. " you can avoid using hardware identifiers, such as SSAID (Android ID) and IMEI, without limiting required functionality."
Rather go for an instance ID such as String uniqueID = UUID.randomUUID().toString(); or FirebaseInstanceId.getInstance().getId();
Not sure about IMEI number, but you can get the simSerialNumber and other carrier info this way.
getSimSerialNumber() needs privileged permissions from Android 10 onwards, and third party apps can't register this permission.
See : https://developer.android.com/about/versions/10/privacy/changes#non-resettable-device-ids
A possible solution is to use the TELEPHONY_SUBSCRIPTION_SERVICE from Android 5.1, to retrieve the sim serial number. Steps below:
Check for READ_PHONE_STATE permission.
Get Active subscription list.( Returns the list of all active sim cards)
Retrieve the sim details from Subscription Object.
if ( isPermissionGranted(READ_PHONE_STATE) ) {
String simSerialNo="";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
SubscriptionManager subsManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
List<SubscriptionInfo> subsList = subsManager.getActiveSubscriptionInfoList();
if (subsList!=null) {
for (SubscriptionInfo subsInfo : subsList) {
if (subsInfo != null) {
simSerialNo = subsInfo.getIccId();
}
}
}
} else {
TelephonyManager tMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
simSerialNo = tMgr.getSimSerialNumber();
}
}
Check if this helps
you can change other way, i use uuid to replace devices id.
String uniquePseudoID = "35" +
Build.BOARD.length() % 10 +
Build.BRAND.length() % 10 +
Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 +
Build.HOST.length() % 10 +
Build.ID.length() % 10 +
Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 +
Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 +
Build.TYPE.length() % 10 +
Build.USER.length() % 10;
String serial = Build.getRadioVersion();
String uuid = new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
AppLog.d("Device ID",uuid);
If your app targets Android 10 or higher, a SecurityException occurs.
Following modules are affected...
Build
getSerial()
TelephonyManager
getImei()
getDeviceId()
getMeid()
getSimSerialNumber()
getSubscriberId()
So you cant get IMEI no for android 10 , You have to used another unique identifier for this like Android ID
It unique 64 bit hex no for device
private String android_id = Secure.getString(getContext().getContentResolver(),
Secure.ANDROID_ID);
According to google docs.
Restriction on non-resettable device identifiers
Starting in Android 10, apps must have the READ_PRIVILEGED_PHONE_STATE privileged permission in order to access the device's non-resettable identifiers, which include both IMEI and serial number.
Caution: Third-party apps installed from the Google Play Store cannot
declare privileged permissions.
So, Instead of imei you can get Android unique ID.
String imei = "";
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (telephonyManager != null) {
try {
imei = telephonyManager.getImei();
} catch (Exception e) {
e.printStackTrace();
imei = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
}
}
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_PHONE_STATE}, 1010);
}
} else {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (telephonyManager != null) {
imei = telephonyManager.getDeviceId();
}
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_PHONE_STATE}, 1010);
}
}
Targeting Android Q, third party apps can't access IMEI at all. Android Q doc is misleading while stating
Starting in Android Q, apps must have the READ_PRIVILEGED_PHONE_STATE
privileged permission in order to access the device's non-resettable
identifiers, which include both IMEI and serial number.
https://developer.android.com/preview/privacy/data-identifiers#device-ids
But when I actually tried to implement it, I am receiving this exception:
java.lang.SecurityException: getDeviceId: The user 10132 does not meet the requirements to access device identifiers.
Someone had reported this on google's issue tracker where a Googler said that this is intended behaviour and IMEI on Q+ is only available for system level apps.
Status: Won't Fix (Intended Behavior) This is Working As Intended.
IMEI is a personal identifier and this is not given out to apps as a
matter of policy. There is no workaround.
https://issuetracker.google.com/issues/129583175#comment10
They mentioned:
If your app is the device or profile owner app, you need only the READ_PHONE_STATE permission to access non-resettable device identifiers, even if your app targets Android 10 or higher.
I tried deploying via EMM as device owner app but not success.
If you needed, you can try to install a work profile in to the mobile phone and include your app in the same package or vice versa.
I tried and it works, it's simple if yo follow this repo: https://github.com/googlesamples/android-testdpc
When you install the Work Profile your app is installed in this profile and you will have acces to the IMEI.
And now there is another example fixed yesterday to Android 10:
https://github.com/android/enterprise-samples/pull/29

I am getting IMEI null in Android Q?

I am getting the IMEI ID null from the telephonymanager. What to do?
is there any workaround for that?
Android Q has restricted to access for both IMEI and serial no. It is available only for platform and apps with special carrier permission. Also the permission READ_PRIVILEGED_PHONE_STATE is not available for non platform apps.
If you try to access it throws below exception
java.lang.SecurityException: getImeiForSlot: The user 10180 does not meet the requirements to access device identifiers.
Please refer documentation:
https://developer.android.com/preview/privacy/data-identifiers#device-ids
Also refer Issue
I am late to post answer. I still believe my answer will help someone.
Android 10 Restricted developer to Access IMEI number.
You can have a alternate solution by get Software ID. You can use software id as a unique id. Please find below code as i use in Application.
public static String getDeviceId(Context context) {
String deviceId;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
deviceId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID);
} else {
final TelephonyManager mTelephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
deviceId = mTelephony.getDeviceId();
} else {
deviceId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID);
}
}
return deviceId;
}
This just would not work as of Android Q. Third party apps can not use IMEI nor the serial number of a phone and other non-resettable device identifiers.
The only permissions that are able to use those is READ_PRIVILEGED_PHONE_STATE and that cannot be used by any third party apps - Manufacture and Software Applications. If you use that method you will get an error Security exception or get null .
You can still try to get a unique id by using:
import android.provider.Settings.Secure;
private String android_id = Secure.getString(getContext().getContentResolver(),Secure.ANDROID_ID);
The best way to get the IMEI number is as follows:
public static String getIMEIDeviceId(Context context) {
String deviceId;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
} else {
final TelephonyManager mTelephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
return "";
}
}
assert mTelephony != null;
if (mTelephony.getDeviceId() != null)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
deviceId = mTelephony.getImei();
}else {
deviceId = mTelephony.getDeviceId();
}
} else {
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
}
Log.d("deviceId", deviceId);
return deviceId;
}
Just copy the method and use it. It will definitely. However, you might know you can't get IMEI in android Q (version 10). In this code, you can get a unique identifier (alternative id) through any device or any API level.
It works 100%
Thank You!!
And Enjoy Coding :)
As the best practices suggest. " you can avoid using hardware identifiers, such as SSAID (Android ID) and IMEI, without limiting required functionality."
Rather go for an instance ID such as String uniqueID = UUID.randomUUID().toString(); or FirebaseInstanceId.getInstance().getId();
Not sure about IMEI number, but you can get the simSerialNumber and other carrier info this way.
getSimSerialNumber() needs privileged permissions from Android 10 onwards, and third party apps can't register this permission.
See : https://developer.android.com/about/versions/10/privacy/changes#non-resettable-device-ids
A possible solution is to use the TELEPHONY_SUBSCRIPTION_SERVICE from Android 5.1, to retrieve the sim serial number. Steps below:
Check for READ_PHONE_STATE permission.
Get Active subscription list.( Returns the list of all active sim cards)
Retrieve the sim details from Subscription Object.
if ( isPermissionGranted(READ_PHONE_STATE) ) {
String simSerialNo="";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
SubscriptionManager subsManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
List<SubscriptionInfo> subsList = subsManager.getActiveSubscriptionInfoList();
if (subsList!=null) {
for (SubscriptionInfo subsInfo : subsList) {
if (subsInfo != null) {
simSerialNo = subsInfo.getIccId();
}
}
}
} else {
TelephonyManager tMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
simSerialNo = tMgr.getSimSerialNumber();
}
}
Check if this helps
you can change other way, i use uuid to replace devices id.
String uniquePseudoID = "35" +
Build.BOARD.length() % 10 +
Build.BRAND.length() % 10 +
Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 +
Build.HOST.length() % 10 +
Build.ID.length() % 10 +
Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 +
Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 +
Build.TYPE.length() % 10 +
Build.USER.length() % 10;
String serial = Build.getRadioVersion();
String uuid = new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
AppLog.d("Device ID",uuid);
If your app targets Android 10 or higher, a SecurityException occurs.
Following modules are affected...
Build
getSerial()
TelephonyManager
getImei()
getDeviceId()
getMeid()
getSimSerialNumber()
getSubscriberId()
So you cant get IMEI no for android 10 , You have to used another unique identifier for this like Android ID
It unique 64 bit hex no for device
private String android_id = Secure.getString(getContext().getContentResolver(),
Secure.ANDROID_ID);
According to google docs.
Restriction on non-resettable device identifiers
Starting in Android 10, apps must have the READ_PRIVILEGED_PHONE_STATE privileged permission in order to access the device's non-resettable identifiers, which include both IMEI and serial number.
Caution: Third-party apps installed from the Google Play Store cannot
declare privileged permissions.
So, Instead of imei you can get Android unique ID.
String imei = "";
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (telephonyManager != null) {
try {
imei = telephonyManager.getImei();
} catch (Exception e) {
e.printStackTrace();
imei = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
}
}
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_PHONE_STATE}, 1010);
}
} else {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (telephonyManager != null) {
imei = telephonyManager.getDeviceId();
}
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_PHONE_STATE}, 1010);
}
}
Targeting Android Q, third party apps can't access IMEI at all. Android Q doc is misleading while stating
Starting in Android Q, apps must have the READ_PRIVILEGED_PHONE_STATE
privileged permission in order to access the device's non-resettable
identifiers, which include both IMEI and serial number.
https://developer.android.com/preview/privacy/data-identifiers#device-ids
But when I actually tried to implement it, I am receiving this exception:
java.lang.SecurityException: getDeviceId: The user 10132 does not meet the requirements to access device identifiers.
Someone had reported this on google's issue tracker where a Googler said that this is intended behaviour and IMEI on Q+ is only available for system level apps.
Status: Won't Fix (Intended Behavior) This is Working As Intended.
IMEI is a personal identifier and this is not given out to apps as a
matter of policy. There is no workaround.
https://issuetracker.google.com/issues/129583175#comment10
They mentioned:
If your app is the device or profile owner app, you need only the READ_PHONE_STATE permission to access non-resettable device identifiers, even if your app targets Android 10 or higher.
I tried deploying via EMM as device owner app but not success.
If you needed, you can try to install a work profile in to the mobile phone and include your app in the same package or vice versa.
I tried and it works, it's simple if yo follow this repo: https://github.com/googlesamples/android-testdpc
When you install the Work Profile your app is installed in this profile and you will have acces to the IMEI.
And now there is another example fixed yesterday to Android 10:
https://github.com/android/enterprise-samples/pull/29

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 "";
}

The setMobileDataEnabled method is no longer callable as of Android L and later

I have logged Issue 78084 with Google regarding the setMobileDataEnabled() method being no longer callable via reflection. It was callable since Android 2.1 (API 7) to Android 4.4 (API 19) via reflection, but as of Android L and later, even with root, the setMobileDataEnabled() method is not callable.
The official response is that the issue is "Closed" and the status set to "WorkingAsIntended". Google's simple explanation is:
Private APIs are private because they are not stable and might disappear without notice.
Yes, Google, we are aware of the risk of using reflection to call hidden method- even before Android came on the scene- but you need to provide a more solid answer as to alternatives, if any, for accomplishing the same result as setMobileDataEnabled(). (If you are displeased with Google's decision as I am, then log into Issue 78084 and star it as many as possible to let Google know the error of their way.)
So, my question to you is: Are we at a dead end when it comes to programmatically enable or disable mobile network function on an Android device? This heavy-handed approach from Google somehow does not sit well with me. If you have workaround for Android 5.0 (Lollipop) and beyond, I would love to hear your answer/discussion in this thread.
I have used the code below to see if the setMobileDataEnabled() method is available:
final Class<?> conmanClass = Class.forName(context.getSystemService(Context.CONNECTIVITY_SERVICE).getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(context.getSystemService(Context.CONNECTIVITY_SERVICE));
final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method[] methods = iConnectivityManagerClass.getDeclaredMethods();
for (final Method method : methods) {
if (method.toGenericString().contains("set")) {
Log.i("TESTING", "Method: " + method.getName());
}
}
But it's not.
UPDATE: Currently, it's possible to toggle mobile network if the device is rooted. However, for non-rooted devices, it's still an investigative process as there is no universal method to toggle mobile network.
To extend Muzikant's Solution #2, can someone please try the solution below on an Android 5.0 rooted device (as I currently do not possess one) and let me know if it works or does not work.
To enable or disable mobile data, try:
// 1: Enable; 0: Disable
su -c settings put global mobile_data 1
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Note: The mobile_data variable can be found in Android API 21 source codes at /android-sdk/sources/android-21/android/provider/Settings.java and is declared as:
/**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* #hide
*/
public static final String MOBILE_DATA = "mobile_data";
While the android.intent.action.ANY_DATA_STATE Intent can be found in Android API 21 source codes at /android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java and is declared as:
/**
* Broadcast Action: The data connection state has changed for any one of the
* phone's mobile data connections (eg, default, MMS or GPS specific connection).
*
* <p class="note">
* Requires the READ_PHONE_STATE permission.
* <p class="note">This is a protected intent that can only be sent by the system.
*
*/
public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
= "android.intent.action.ANY_DATA_STATE";
UPDATE 1: If you don't want to implement the above Java codes in your Android application, then you can run the su commands via a shell (Linux) or command prompt (Windows) as follow:
adb shell "su -c 'settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1'"
Note: adb is located at /android-sdk/platform-tools/ directory. The settings command is only supported on Android 4.2 or later. Older Android version will report a "sh: settings: not found" error.
UPDATE 2: Another way to toggle mobile network on a rooted Android 5+ device would be to use the undocumented service shell command. The following command can be executed via ADB to toggle mobile network:
// 1: Enable; 0: Disable
adb shell "su -c 'service call phone 83 i32 1'"
Or just:
// 1: Enable; 0: Disable
adb shell service call phone 83 i32 1
Note 1: The transaction code 83 used in the service call phone command might change between Android versions. Please check com.android.internal.telephony.ITelephony for the value of the TRANSACTION_setDataEnabled field for your version of Android. Also, instead of hardcoding 83, you would be better off using Reflection to get the value of the TRANSACTION_setDataEnabled field. This way, it will work across all mobile brands running Android 5+ (If you don't know how to use Reflection to get the value of the TRANSACTION_setDataEnabled field, see solution from PhongLe below- save me from duplicating it here.) Important: Please note that transaction code TRANSACTION_setDataEnabled has only been introduced in Android 5.0 and later versions. Running this transaction code on earlier versions of Android will do nothing as the transaction code TRANSACTION_setDataEnabled does not exist.
Note 2: adb is located at /android-sdk/platform-tools/ directory. If you do not wish to use ADB, execute the method via su in your app.
Note 3: See UPDATE 3 below.
UPDATE 3: Many Android developers have emailed me questions regarding switching mobile network on/off for Android 5+, but instead of answering individual emails, I'll post my answer here so everyone can use it and adapt it for their Android apps.
First thing first, let's clear up some misconception and misunderstanding regarding:
svc data enable
svc data disable
The above methods would only turn background data on/off, not the subscription service, so the battery will drain a fair bit since the subscription service- an Android system service- will still be running in the background. For Android devices supporting multiple sim cards, this scenario is worse as the subscription service constantly scans for available mobile network(s) to use with the active SIM cards available in the Android device. Use this method at your own risk.
Now, the proper way to switch off mobile network, including its corresponding subscription service via the SubscriptionManager class introduced in API 22, is:
public static void setMobileNetworkfromLollipop(Context context) throws Exception {
String command = null;
int state = 0;
try {
// Get the current state of the mobile network.
state = isMobileDataEnabledFromLollipop(context) ? 0 : 1;
// Get the value of the "TRANSACTION_setDataEnabled" field.
String transactionCode = getTransactionCode(context);
// Android 5.1+ (API 22) and later.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
// Loop through the subscription list i.e. SIM list.
for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) {
if (transactionCode != null && transactionCode.length() > 0) {
// Get the active subscription ID for a given SIM card.
int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId();
// Execute the command via `su` to turn off
// mobile network for a subscription service.
command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0 (API 21) only.
if (transactionCode != null && transactionCode.length() > 0) {
// Execute the command via `su` to turn off mobile network.
command = "service call phone " + transactionCode + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} catch(Exception e) {
// Oops! Something went wrong, so we throw the exception here.
throw e;
}
}
To check if the mobile network is enabled or not:
private static boolean isMobileDataEnabledFromLollipop(Context context) {
boolean state = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
state = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
}
return state;
}
To get the value of the TRANSACTION_setDataEnabled field (borrowed from PhongLe's solution below):
private static String getTransactionCode(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
To execute command via su:
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i=0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Hope this update clears up any misconception, misunderstanding, or question you may have about switching mobile network on/off on rooted Android 5+ devices.
Just to share a few more insights and possible solution (for rooted devices and system apps).
Solution #1
It seems like the setMobileDataEnabled method no longer exists in ConnectivityManager and this functionality was moved to TelephonyManager with two methods getDataEnabled and setDataEnabled.
I tried calling these methods with reflection as you can see in the code below:
public void setMobileDataState(boolean mobileDataEnabled)
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method setMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
if (null != setMobileDataEnabledMethod)
{
setMobileDataEnabledMethod.invoke(telephonyService, mobileDataEnabled);
}
}
catch (Exception ex)
{
Log.e(TAG, "Error setting mobile data state", ex);
}
}
public boolean getMobileDataState()
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method getMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("getDataEnabled");
if (null != getMobileDataEnabledMethod)
{
boolean mobileDataEnabled = (Boolean) getMobileDataEnabledMethod.invoke(telephonyService);
return mobileDataEnabled;
}
}
catch (Exception ex)
{
Log.e(TAG, "Error getting mobile data state", ex);
}
return false;
}
When executing the code you get a SecurityException stating that Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.
So, yes this is an intended change to the internal API and is no longer available to apps that used that hack in previous versions.
(start rant: that dreadful android.permission.MODIFY_PHONE_STATE permission... end rant).
The good news are that in case you are building an app that can acquire the MODIFY_PHONE_STATE permission (only system apps can use that), you can use the above code to toggle mobile data state.
Solution #2
To check for current state of mobile data you can use the mobile_data field of Settings.Global (not documented in official documentation).
Settings.Global.getInt(contentResolver, "mobile_data");
And to enable/disable mobile data you can use shell commands on rooted devices (Just basic testing performed so any feedback in comments is appreciated).
You can run the following command(s) as root (1=enable, 0=disable):
settings put global mobile_data 1
settings put global mobile_data 0
I noticed that the service call method posted by ChuongPham does not work consistently on all devices.
I have found the following solution which, I think, will work without any issue on all ROOTED devices.
Execute the following via su
To enable mobile data
svc data enable
To disable mobile data
svc data disable
I think this is the simplest and best method.
Edit:
2 downvotes were for what I believe to be commercial reasons. The person has deleted his comment now. Try it yourself, it works!
Also confirmed to work by guys in comments.
I don't have enough reputation to comment but I have tried all the answers and found the following:
ChuongPham: Instead of using 83, I used reflection to get the value of the variable TRANSACTION_setDataEnabled from the com.android.internal.telephony.ITelephony so it works across all Android 5+ devices, regardless of brands.
Muzikant: Work if the app is moved to /system/priv-app/ directory (thanks to rgruet.) Else, it works via root, too! You just need to inform your users that the app will need a reboot before the changes to the mobile network will take place.
AJ: Work- sort of. Does not turn off subscription service so the devices I tested drained their batteries a fair bit. AJ's solution is NOT equivalent to Muzikant's solution despite the claim. I can confirm this by debugging different Samsung, Sony, and LG stock ROMs (I'm thorough) and can disprove AJ's claim that his solution is the same as Muzikant's. (Note: I can't get my hands on some Nexus and Motorola ROMs so haven't tested these ROMs with the proposed solutions.)
Anyway, hope it clears up any doubt over the solutions.
Happy coding!
PL, Germany
UPDATE: For those wondering how to get the value of the TRANSACTION_setDataEnabled field via reflection, you can do the following:
private static String getTransactionCodeFromApi20(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
I found that su -c 'service call phone 83 i32 1' solution is most reliable for rooted devices. Thanks to Phong Le reference I have improved it by getting vendor/os specific transaction code using reflection. Maybe it will be useful for someone else. So, here is source code:
public void changeConnection(boolean enable) {
try{
StringBuilder command = new StringBuilder();
command.append("su -c ");
command.append("service call phone ");
command.append(getTransactionCode() + " ");
if (Build.VERSION.SDK_INT >= 22) {
SubscriptionManager manager = SubscriptionManager.from(context);
int id = 0;
if (manager.getActiveSubscriptionInfoCount() > 0)
id = manager.getActiveSubscriptionInfoList().get(0).getSubscriptionId();
command.append("i32 ");
command.append(String.valueOf(id) + " ");
}
command.append("i32 ");
command.append(enable?"1":"0");
command.append("\n");
Runtime.getRuntime().exec(command.toString());
}catch(IOException e){
...
}
}
private String getTransactionCode() {
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());
Class stub = ITelephonyClass.getDeclaringClass();
Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
if (Build.VERSION.SDK_INT >= 22)
return "86";
else if (Build.VERSION.SDK_INT == 21)
return "83";
}
return "";
}
Update:
Some of my users report that they have problem with turning ON mobile network via this method (turning off works correct). Does anyone have solution?
Update2:
After some digging the Android 5.1 code I've found that they changed signature of transaction. Android 5.1 brings official support of multi-SIM. So, the transaction need so-called Subscription Id as first parameter (read more here). The result of this situation is that the command su -c 'service call phone 83 i32 1' doesn't turn on Mobile Net on Android 5.1. So, the full command on Android 5.1 should be like this su -c 'service call phone 83 i32 0 i32 1' (the i32 0 is the subId, the i32 1 is command 0 - off and 1 - on). I've update the code above with this fix.
Solution #1 from Muzikant seems to work if you make the app "system" by moving the .apk to the /system/priv-app/ folder, not to the /system/app/ one (#jaumard: maybe that's why your test didn't work).
When the .apk is in the /system/priv-app/ folder, it can successfully request the dreadful android.permission.MODIFY_PHONE_STATE permission in the Manifest and call TelephonyManager.setDataEnabled and TelephonyManager.getDataEnabled.
At least that works on Nexus 5/ Android 5.0. The .apk perms are 0144. You need to reboot the device for the change to be taken into account, maybe this could be avoided - see this thread.
I derived final code from #ChuongPham and #A.J. for enable and disable cellular data. for enable you can call setMobileDataEnabled(true); and for disable you can call setMobileDataEnabled(false);
public void setMobileDataEnabled(boolean enableOrDisable) throws Exception {
String command = null;
if (enableOrDisable) {
command = "svc data enable";
} else {
command = "svc data disable";
}
executeCommandViaSu(mContext, "-c", command);
}
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i = 0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Not all phones and versions of android have things Enable/disable mobile data the same.
otherwise, this solution is tested on my phone (SAMSUNG SM-J100H)
To enable mobile data :
adb shell service call phone 27
To disable mobile data :
adb shell service call phone 28
To correct Muzikant Solution #2
settings put global mobile_data 1
Does enable only the toggle for mobile data but does nothing to the connectivity. Only the toggle is enabled. In order to get the data working using
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Gives error as the extra for
android.intent.action.ANY_DATA_STATE
Requires String Object while --ez parameter is used for boolean. Ref: PhoneGlobals.java & PhoneConstants.java. After using connecting or connected as extra using command
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting
Still doesnt do anything to enable the data.
The following solution works by enabling and disabling mobile data (as you would by clicking on the slider "Mobile data"). Requires root. Tested on LineageOS 16.0 (rooted):
Steps 1-3 are taken from the accepted answer at Turning off second SIM via adb shell/Tasker - using activities/intents:
Download jadx from https://github.com/skylot/jadx
ADB pull the devices framework.jar (adb pull /system/framework/framework.jar)
Open the .jar file with 7-Zip and extract the *.dex files. Open each .dex file with jadx-gui until you find the one with the following tree: com.android.internal.telephony.ITelephony
Find TRANSACTION_enableDataConnectivity and TRANSACTION_disableDataConnectivity, for me these are 38 and 39 respectively
From a root shell (e.g., adb shell or Termux), run service call phone 38 to enable data, and service call phone 39 to disable data.

how to count 3g traffic in android mobile?

What I want to do is to count 3G traffic and WiFi traffic respectively. Now I know how to do with WiFi. Below is the source code for WiFi. By this way I can count WiFi traffic for all the android phones of all manufactures. But I haven't found a similar way for 3g. Does anyone know?
//to get wifi interface
private static String getProp(String prop){
String output = "";
try{
Class<?> sp = Class.forName("android.os.SystemProperites");
Method get = sp.getMethod("get",String.class);
output = (String)get.invoke(null,prop);
}catch(Exception e){
e.printStackTrace();
}
return output;
}
//to get the traffic from system file
...
...
if (connectinTpe == ConnectivityManager.TYPE_WIFI){
String wifiInterface = getProp("wifi.interface");
if(wifiInterface == null || "".equals(wifiInterface)) wifiInterface = "eth0";
rxFile = "/sys/class/net/" +wifiInterface+ "/statistics/rx_bytes";
txFile = "/sys/class/net/" +wifiInterface+ "/statistics/tx_bytes";
}
...
...
Starting from API level 8 (Android 2.2) there is a class TrafficStats which provides what you need:
Class that provides network traffic statistics. These statistics
include bytes transmitted and received and network packets transmitted
and received, over all interfaces, over the mobile interface, and on a
per-UID basis.
On the older versions you can use the approach you mentioned (i.e. reading file content of /sys/class/net/... files). This blog post contains an excellent mapping between TrafficStats methods and file locations. And this SO post contains the source its author used to read those files values. According to it you should first try to read number from "/sys/class/net/rmnet0/statistics/rx_bytes" file (for "received bytes" value) and if it fails try "/sys/class/net/ppp0/statistics/rx_bytes" instead.
to get the current type of connection you can use the TelephonyManager: http://developer.android.com/reference/android/telephony/TelephonyManager.html
first check if the device is connected to the default mobile data connection and then check the connection type:
if (connectinTpe == ConnectivityManager.TYPE_MOBILE)
{
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
int curConnectionType = tm.getNetworkType();
if(curConnectionType >= /*connection type you are looking for*/)
{
// do what you want
}
}

Categories

Resources