Получение истории использования мобильных данных с помощью NetworkStatsManager

Я хочу знать историю использования данных и заметил "новый" android-6 NetworkStatsManager, который кажется положительным (я использовал TrafficStats в то время, но это не будет покрывать что-либо ранее перезагрузкой).

Из документации API:

ПРИМЕЧАНИЕ. Для этого API требуется разрешение PACKAGE_USAGE_STATS, которое является на системном уровне и не будет предоставляться сторонним приложениям. Тем не менее, объявление разрешения подразумевает намерение использовать API и пользователь устройства может предоставить разрешение через Настройки выражение. Приложениям для владельцев профиля автоматически предоставляются разрешения для запроса данных о профиле, который они управляют (то есть для любого запроса кроме querySummaryForDevice (int, String, long, long)). Владелец устройства приложения также получают доступ к данным об использовании основного пользователя.

Я хочу узнать об использовании данных на агрегированном уровне, а не о том, какое приложение использует эти данные, поэтому я попытался использовать его следующим образом:

NetworkStatsManager service = context.getSystemService(NetworkStatsManager.class);

NetworkStats.Bucket bucket = 
        service.querySummaryForDevice(ConnectivityManager.TYPE_MOBILE, null, from, to);
...

К сожалению, это вызывает a SecurityException:

java.lang.SecurityException: NetworkStats: Neither user 10174 nor current process has android.permission.READ_NETWORK_USAGE_HISTORY.
at android.os.Parcel.readException(Parcel.java:1620)
at android.os.Parcel.readException(Parcel.java:1573)
at android.net.INetworkStatsSession$Stub$Proxy.getDeviceSummaryForNetwork(INetworkStatsSession.java:259)
at android.app.usage.NetworkStats.getDeviceSummaryForNetwork(NetworkStats.java:316)
at android.app.usage.NetworkStatsManager.querySummaryForDevice(NetworkStatsManager.java:100)
...

Разрешение android.permission.READ_NETWORK_USAGE_HISTORY не разрешено для сторонних приложений. Так что это казалось тупиком.

Однако я немного просверлил внутренности и выяснил, что вы можете использовать внутренний/скрытый API для выполнения того же самого действия без запроса каких-либо разрешений:

INetworkStatsService service =
        INetworkStatsService.Stub.asInterface(
                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));

INetworkStatsSession session = service.openSession();

NetworkTemplate mobileTemplate = NetworkTemplate.buildTemplateMobileWildcard();
int fields = NetworkStatsHistory.FIELD_RX_BYTES | NetworkStatsHistory.FIELD_TX_BYTES;

NetworkStatsHistory mobileHistory = session.getHistoryForNetwork(mobileTemplate, fields);

for (int i = 0; i < mobileHistory.size(); i++) {
    NetworkStatsHistory.Entry entry = mobileHistory.getValues(i, null);
    ...
}

session.close();

Я действительно хочу сделать то же самое с общедоступным API, так как я могу это сделать?

Ответы

Ответ 1

Существует способ получить доступ к NetworkStateManager без доступа к частному API. Вот шаги:

  • Объявите необходимые разрешения в AndroidManifest.xml:

    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission
        android:name="android.permission.PACKAGE_USAGE_STATS"
        tools:ignore="ProtectedPermissions"/>
    
    1. Запросить разрешение в Activity

android.permission.PACKAGE_USAGE_STATS не является нормальным разрешением, его просто невозможно запросить. Чтобы проверить, предоставлено ли разрешение, отметьте:

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) {
    return true;
}

Чтобы запросить это разрешение, просто вызовите Intent:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

Также необходим другой разрешающий способ: Manifest.permission.READ_PHONE_STATE. Это необходимо только тогда, когда вам нужно получить статистику мобильных данных. Однако это нормальное разрешение, поэтому может быть запрошено как любое другое разрешение

  1. Используйте NetworkStatsManager:

Сделал образец Github repo, демонстрирующий использование.

Ответ 2

/*
getting youtube usage for both mobile and wifi.
 */
public long getYoutubeTotalusage(Context context) {
    String subId = getSubscriberId(context, ConnectivityManager.TYPE_MOBILE);
    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 "";
}