web-dev-qa-db-ja.com

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);
...

残念ながら、これは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で実行したいので、どうすればそれを実行できますか?

17
dacwe

プライベートAPIにアクセスせずにNetworkStateManagerへのアクセスを取得する方法があります。手順は次のとおりです。

  1. 必要な権限を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 を作成しました。

20
R. Zagórski
/*
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 "";
}
1
rajeesh