/*
* This source is part of the
* _____ ___ ____
* __ / / _ \/ _ | / __/___ _______ _
* / // / , _/ __ |/ _/_/ _ \/ __/ _ `/
* \___/_/|_/_/ |_/_/ (_)___/_/ \_, /
* /___/
* repository.
*
* Copyright (C) 2016 Carmen Alvarez (c@rmen.ca)
*
* 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.app.service.datasources;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.util.List;
import ca.rmen.android.networkmonitor.Constants;
import ca.rmen.android.networkmonitor.app.prefs.NetMonPreferences;
import ca.rmen.android.networkmonitor.provider.NetMonColumns;
import ca.rmen.android.networkmonitor.util.Log;
import ca.rmen.android.networkmonitor.util.PermissionUtil;
/**
* Retrieves the app which has consumed the most data since device boot.
*/
public class ConsumingAppDataSource implements NetMonDataSource {
private static final String TAG = Constants.TAG + ConsumingAppDataSource.class.getSimpleName();
private Context mContext;
private TelephonyManager mTelephonyManager;
private NetworkStatsManager mNetworkStatsManager;
private PackageManager mPackageManager;
private ConnectivityManager mConnectivityManager;
private NetMonPreferences mPrefs;
@Override
public void onCreate(Context context) {
Log.v(TAG, "onCreate");
mContext = context;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mNetworkStatsManager = (NetworkStatsManager) context.getSystemService(Context.NETWORK_STATS_SERVICE);
}
mPackageManager = context.getPackageManager();
mPrefs = NetMonPreferences.getInstance(context);
}
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
}
@Override
public ContentValues getContentValues() {
Log.v(TAG, "getContentValues");
ContentValues values = new ContentValues(2);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return values;
}
if (mPrefs.isFastPollingEnabled()) {
return values;
}
if (!PermissionUtil.hasUsageStatsPermission(mContext)) {
return values;
}
NetworkInfo activeNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo == null) {
return values;
}
List<ApplicationInfo> packages = mPackageManager.getInstalledApplications(
PackageManager.GET_META_DATA);
long maxBytes = 0;
String processName = null;
int activeNetworkType = activeNetworkInfo.getType();
for (ApplicationInfo packageInfo : packages) {
long bytes = getBytesForUid(packageInfo.uid, activeNetworkType);
if (bytes > maxBytes) {
maxBytes = bytes;
processName = packageInfo.processName;
}
}
if (!TextUtils.isEmpty(processName)) {
values.put(NetMonColumns.MOST_CONSUMING_APP_NAME, processName);
values.put(NetMonColumns.MOST_CONSUMING_APP_BYTES, maxBytes);
}
Log.v(TAG, "getContentValues end");
return values;
}
@SuppressLint("HardwareIds")
@TargetApi(Build.VERSION_CODES.M)
private long getBytesForUid(int uid, int networkType) {
Log.v(TAG, "getBytesForUid, uid = " + uid + ", networkType = " + networkType);
try {
String subscriberId;
if (PermissionUtil.hasReadPhoneStatePermission(mContext)) {
subscriberId = mTelephonyManager.getSubscriberId();
} else {
subscriberId = "";
}
NetworkStats stats = mNetworkStatsManager.queryDetailsForUid(
networkType,
subscriberId,
Long.MIN_VALUE,
Long.MAX_VALUE,
uid);
long total = 0;
if (stats == null) return 0;
while (stats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
stats.getNextBucket(bucket);
total += bucket.getRxBytes() + bucket.getTxBytes();
}
Log.v(TAG, "getBytesForUid, uid = " + uid + ", networkType = " + networkType + ", returning " + total);
return total;
}
// I know it's not good to catch a generic RuntimeException, but I saw some undocumented
// IllegalArgumentExceptions using the queryDetailsForUid() method.
catch (RuntimeException | RemoteException e) {
Log.v(TAG, "Error getting network stats for uid " + uid + ": " + e.getMessage(), e);
return 0;
}
}
}