I am building an application which monitors applications network data usage over some interval.
On android 5 I had no problem with TrafficStats, although on android 8 using NetworkStats I always receive same data. I've read that bucket refreshes every 30 minutes or so, so I tried listening to youtube for about 1 hour but still same data usage ( in package com.google.android.youtube ).
So, my question, why does NetworkStats return always same data?
Here is my code:
public class DataService extends Service {
private int interval = 1000 * 5; //Time interval
private List<ApplicationInfo> infos;
private PackageManager packageManager;
private static int counter = 0;
private ArrayList<ValuesHolder> values;
private Context context;
private Handler mHandler;
public static final String DATA_INTENT_FILTER = "Transmitted data updated";
private Runnable mHandlerTask = new Runnable() {
@Override
public void run() {
update();
Intent intent = new Intent();
intent.setAction(DATA_INTENT_FILTER);
sendBroadcast(intent);
mHandler.postDelayed(mHandlerTask, interval);
}
};
@Override
public void onCreate() {
super.onCreate();
mHandler = new Handler();
context = this;
values = new ArrayList<>();
packageManager = getPackageManager();
infos = packageManager.getInstalledApplications(0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
getNonSystemPackages();
}
mHandlerTask.run();
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void getNonSystemPackages() {
PackageManager pm = getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo packageInfo : packages) {
if (pm.getLaunchIntentForPackage(packageInfo.packageName) != null) {
String currAppName = packageInfo.packageName;
try {
int uid = pm.getPackageUid(currAppName, 0);
values.add(counter, new ValuesHolder(currAppName,
getTotalDataForMobile(uid) + getTotalDataForWifi(uid),
uid));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
} else {
//System App
}
}
}
private void update() {
String saveString;
long difference;
long data;
int uid;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
for (int i = 0; i < values.size(); i++) {
uid = values.get(i).getUid();
if (TrafficStats.getUidTxBytes(uid) == TrafficStats.UNSUPPORTED) {
data = getTotalDataForMobile(uid) + getTotalDataForWifi(uid);
//Always same data
Log.i(values.get(i).getPackageName(), " data" + data);
} else {
data = TrafficStats.getUidRxBytes(uid) + TrafficStats.getUidTxBytes(uid);
}
}
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private long getTotalDataForWifi(int uid) {
NetworkStatsManager networkStatsManager = (NetworkStatsManager) getApplicationContext().getSystemService(Context.NETWORK_STATS_SERVICE);
NetworkStats.Bucket bucket;
long totalData = 0;
NetworkStats networkStats = null;
try {
networkStats = networkStatsManager.queryDetailsForUid(
ConnectivityManager.TYPE_WIFI,
"",
0,
System.currentTimeMillis(),
uid);
} catch (RemoteException e) {
return -1;
}
do {
bucket = new NetworkStats.Bucket();
networkStats.getNextBucket(bucket);
totalData += bucket.getRxBytes() + bucket.getTxBytes();
} while (networkStats.hasNextBucket());
return totalData;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private long getTotalDataForMobile(int uid) {
NetworkStatsManager networkStatsManager = (NetworkStatsManager) getApplicationContext().getSystemService(Context.NETWORK_STATS_SERVICE);
NetworkStats.Bucket bucket;
NetworkStats networkStats = null;
long totalData = 0;
try {
networkStats = networkStatsManager.queryDetailsForUid(
ConnectivityManager.TYPE_MOBILE,
"",
0,
System.currentTimeMillis(),
uid);
} catch (RemoteException e) {
return -1;
}
do {
bucket = new NetworkStats.Bucket();
networkStats.getNextBucket(bucket);
totalData += bucket.getRxBytes() + bucket.getTxBytes();
} while (networkStats.hasNextBucket());
return totalData;
}
}
I request runtime permissions in my MainActivity like this:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, Permissions.ARR_PHONE_STATE, Permissions.REQUEST_PHONE_STATE);
}
if
((checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED)
|| (checkSelfPermission(Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED)
|| (checkSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(this, Permissions.ARR_NETWORK, Permissions.REQUEST_NETWORK);
}
AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(), getPackageName());
if (mode == AppOpsManager.MODE_ALLOWED) {
} else {
}
if (!Permissions.checkForPermission(this)) {
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
} else {
Log.i("SYSTEM", "PERMISSION GRANTED");
}
}
Permissions are granted, I request them in android manifest as well.
Thanks for any help.
Answering my own question, for someone who might have same problem. I figured out that with method queryDetailsForUid are data refreshed in quite large interval ( 3 hours or so ).
Here is solution that works well:
private long getApplicationUsage(int networkType, int uid) {
long usage = 0L;
NetworkStats networkStatsByApp;
NetworkStatsManager networkStatsManager = (NetworkStatsManager) getApplicationContext().getSystemService(Context.NETWORK_STATS_SERVICE);
try {
networkStatsByApp = networkStatsManager.querySummary(networkType, "", 0, System.currentTimeMillis());
do {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
networkStatsByApp.getNextBucket(bucket);
if (bucket.getUid() == uid) {
// in some devices this is immediately looping twice
// and the second iteration is returning correct value.
// So result is returned in the end.
usage = (bucket.getRxBytes() + bucket.getTxBytes());
}
} while (networkStatsByApp.hasNextBucket());
} catch (RemoteException e) {
e.printStackTrace();
}
return usage;
}
On application start just save total result, then after desired interval substract total usage with your saved value and you have got the result. ( don't forget to save new result ).