diff options
Diffstat (limited to 'framework-t/src')
21 files changed, 8481 insertions, 0 deletions
diff --git a/framework-t/src/android/app/usage/NetworkStats.java b/framework-t/src/android/app/usage/NetworkStats.java new file mode 100644 index 0000000000..216a4a0987 --- /dev/null +++ b/framework-t/src/android/app/usage/NetworkStats.java @@ -0,0 +1,717 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * 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 android.app.usage; + +import android.annotation.IntDef; +import android.content.Context; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.RemoteException; +import android.util.IntArray; +import android.util.Log; + +import dalvik.system.CloseGuard; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects + * are returned as results to various queries in {@link NetworkStatsManager}. + */ +public final class NetworkStats implements AutoCloseable { + private final static String TAG = "NetworkStats"; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + /** + * Start timestamp of stats collected + */ + private final long mStartTimeStamp; + + /** + * End timestamp of stats collected + */ + private final long mEndTimeStamp; + + /** + * Non-null array indicates the query enumerates over uids. + */ + private int[] mUids; + + /** + * Index of the current uid in mUids when doing uid enumeration or a single uid value, + * depending on query type. + */ + private int mUidOrUidIndex; + + /** + * Tag id in case if was specified in the query. + */ + private int mTag = android.net.NetworkStats.TAG_NONE; + + /** + * State in case it was not specified in the query. + */ + private int mState = Bucket.STATE_ALL; + + /** + * The session while the query requires it, null if all the stats have been collected or close() + * has been called. + */ + private INetworkStatsSession mSession; + private NetworkTemplate mTemplate; + + /** + * Results of a summary query. + */ + private android.net.NetworkStats mSummary = null; + + /** + * Results of detail queries. + */ + private NetworkStatsHistory mHistory = null; + + /** + * Where we are in enumerating over the current result. + */ + private int mEnumerationIndex = 0; + + /** + * Recycling entry objects to prevent heap fragmentation. + */ + private android.net.NetworkStats.Entry mRecycledSummaryEntry = null; + private NetworkStatsHistory.Entry mRecycledHistoryEntry = null; + + /** @hide */ + NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, + long endTimestamp, INetworkStatsService statsService) + throws RemoteException, SecurityException { + // Open network stats session + mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName()); + mCloseGuard.open("close"); + mTemplate = template; + mStartTimeStamp = startTimestamp; + mEndTimeStamp = endTimestamp; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } + } + + // -------------------------BEGINNING OF PUBLIC API----------------------------------- + + /** + * Buckets are the smallest elements of a query result. As some dimensions of a result may be + * aggregated (e.g. time or state) some values may be equal across all buckets. + */ + public static class Bucket { + /** @hide */ + @IntDef(prefix = { "STATE_" }, value = { + STATE_ALL, + STATE_DEFAULT, + STATE_FOREGROUND + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + + /** + * Combined usage across all states. + */ + public static final int STATE_ALL = -1; + + /** + * Usage not accounted for in any other state. + */ + public static final int STATE_DEFAULT = 0x1; + + /** + * Foreground usage. + */ + public static final int STATE_FOREGROUND = 0x2; + + /** + * Special UID value for aggregate/unspecified. + */ + public static final int UID_ALL = android.net.NetworkStats.UID_ALL; + + /** + * Special UID value for removed apps. + */ + public static final int UID_REMOVED = TrafficStats.UID_REMOVED; + + /** + * Special UID value for data usage by tethering. + */ + public static final int UID_TETHERING = TrafficStats.UID_TETHERING; + + /** @hide */ + @IntDef(prefix = { "METERED_" }, value = { + METERED_ALL, + METERED_NO, + METERED_YES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Metered {} + + /** + * Combined usage across all metered states. Covers metered and unmetered usage. + */ + public static final int METERED_ALL = -1; + + /** + * Usage that occurs on an unmetered network. + */ + public static final int METERED_NO = 0x1; + + /** + * Usage that occurs on a metered network. + * + * <p>A network is classified as metered when the user is sensitive to heavy data usage on + * that connection. + */ + public static final int METERED_YES = 0x2; + + /** @hide */ + @IntDef(prefix = { "ROAMING_" }, value = { + ROAMING_ALL, + ROAMING_NO, + ROAMING_YES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Roaming {} + + /** + * Combined usage across all roaming states. Covers both roaming and non-roaming usage. + */ + public static final int ROAMING_ALL = -1; + + /** + * Usage that occurs on a home, non-roaming network. + * + * <p>Any cellular usage in this bucket was incurred while the device was connected to a + * tower owned or operated by the user's wireless carrier, or a tower that the user's + * wireless carrier has indicated should be treated as a home network regardless. + * + * <p>This is also the default value for network types that do not support roaming. + */ + public static final int ROAMING_NO = 0x1; + + /** + * Usage that occurs on a roaming network. + * + * <p>Any cellular usage in this bucket as incurred while the device was roaming on another + * carrier's network, for which additional charges may apply. + */ + public static final int ROAMING_YES = 0x2; + + /** @hide */ + @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { + DEFAULT_NETWORK_ALL, + DEFAULT_NETWORK_NO, + DEFAULT_NETWORK_YES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DefaultNetworkStatus {} + + /** + * Combined usage for this network regardless of default network status. + */ + public static final int DEFAULT_NETWORK_ALL = -1; + + /** + * Usage that occurs while this network is not a default network. + * + * <p>This implies that the app responsible for this usage requested that it occur on a + * specific network different from the one(s) the system would have selected for it. + */ + public static final int DEFAULT_NETWORK_NO = 0x1; + + /** + * Usage that occurs while this network is a default network. + * + * <p>This implies that the app either did not select a specific network for this usage, + * or it selected a network that the system could have selected for app traffic. + */ + public static final int DEFAULT_NETWORK_YES = 0x2; + + /** + * Special TAG value for total data across all tags + */ + public static final int TAG_NONE = android.net.NetworkStats.TAG_NONE; + + private int mUid; + private int mTag; + private int mState; + private int mDefaultNetworkStatus; + private int mMetered; + private int mRoaming; + private long mBeginTimeStamp; + private long mEndTimeStamp; + private long mRxBytes; + private long mRxPackets; + private long mTxBytes; + private long mTxPackets; + + private static int convertSet(@State int state) { + switch (state) { + case STATE_ALL: return android.net.NetworkStats.SET_ALL; + case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT; + case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND; + } + return 0; + } + + private static @State int convertState(int networkStatsSet) { + switch (networkStatsSet) { + case android.net.NetworkStats.SET_ALL : return STATE_ALL; + case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT; + case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND; + } + return 0; + } + + private static int convertUid(int uid) { + switch (uid) { + case TrafficStats.UID_REMOVED: return UID_REMOVED; + case TrafficStats.UID_TETHERING: return UID_TETHERING; + } + return uid; + } + + private static int convertTag(int tag) { + switch (tag) { + case android.net.NetworkStats.TAG_NONE: return TAG_NONE; + } + return tag; + } + + private static @Metered int convertMetered(int metered) { + switch (metered) { + case android.net.NetworkStats.METERED_ALL : return METERED_ALL; + case android.net.NetworkStats.METERED_NO: return METERED_NO; + case android.net.NetworkStats.METERED_YES: return METERED_YES; + } + return 0; + } + + private static @Roaming int convertRoaming(int roaming) { + switch (roaming) { + case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL; + case android.net.NetworkStats.ROAMING_NO: return ROAMING_NO; + case android.net.NetworkStats.ROAMING_YES: return ROAMING_YES; + } + return 0; + } + + private static @DefaultNetworkStatus int convertDefaultNetworkStatus( + int defaultNetworkStatus) { + switch (defaultNetworkStatus) { + case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL; + case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO; + case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES; + } + return 0; + } + + public Bucket() { + } + + /** + * Key of the bucket. Usually an app uid or one of the following special values:<p /> + * <ul> + * <li>{@link #UID_REMOVED}</li> + * <li>{@link #UID_TETHERING}</li> + * <li>{@link android.os.Process#SYSTEM_UID}</li> + * </ul> + * @return Bucket key. + */ + public int getUid() { + return mUid; + } + + /** + * Tag of the bucket.<p /> + * @return Bucket tag. + */ + public int getTag() { + return mTag; + } + + /** + * Usage state. One of the following values:<p/> + * <ul> + * <li>{@link #STATE_ALL}</li> + * <li>{@link #STATE_DEFAULT}</li> + * <li>{@link #STATE_FOREGROUND}</li> + * </ul> + * @return Usage state. + */ + public @State int getState() { + return mState; + } + + /** + * Metered state. One of the following values:<p/> + * <ul> + * <li>{@link #METERED_ALL}</li> + * <li>{@link #METERED_NO}</li> + * <li>{@link #METERED_YES}</li> + * </ul> + * <p>A network is classified as metered when the user is sensitive to heavy data usage on + * that connection. Apps may warn before using these networks for large downloads. The + * metered state can be set by the user within data usage network restrictions. + */ + public @Metered int getMetered() { + return mMetered; + } + + /** + * Roaming state. One of the following values:<p/> + * <ul> + * <li>{@link #ROAMING_ALL}</li> + * <li>{@link #ROAMING_NO}</li> + * <li>{@link #ROAMING_YES}</li> + * </ul> + */ + public @Roaming int getRoaming() { + return mRoaming; + } + + /** + * Default network status. One of the following values:<p/> + * <ul> + * <li>{@link #DEFAULT_NETWORK_ALL}</li> + * <li>{@link #DEFAULT_NETWORK_NO}</li> + * <li>{@link #DEFAULT_NETWORK_YES}</li> + * </ul> + */ + public @DefaultNetworkStatus int getDefaultNetworkStatus() { + return mDefaultNetworkStatus; + } + + /** + * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Start of interval. + */ + public long getStartTimeStamp() { + return mBeginTimeStamp; + } + + /** + * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return End of interval. + */ + public long getEndTimeStamp() { + return mEndTimeStamp; + } + + /** + * Number of bytes received during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of bytes. + */ + public long getRxBytes() { + return mRxBytes; + } + + /** + * Number of bytes transmitted during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of bytes. + */ + public long getTxBytes() { + return mTxBytes; + } + + /** + * Number of packets received during the bucket's time interval. Statistics are measured at + * the network layer, so they include both TCP and UDP usage. + * @return Number of packets. + */ + public long getRxPackets() { + return mRxPackets; + } + + /** + * Number of packets transmitted during the bucket's time interval. Statistics are measured + * at the network layer, so they include both TCP and UDP usage. + * @return Number of packets. + */ + public long getTxPackets() { + return mTxPackets; + } + } + + /** + * Fills the recycled bucket with data of the next bin in the enumeration. + * @param bucketOut Bucket to be filled with data. + * @return true if successfully filled the bucket, false otherwise. + */ + public boolean getNextBucket(Bucket bucketOut) { + if (mSummary != null) { + return getNextSummaryBucket(bucketOut); + } else { + return getNextHistoryBucket(bucketOut); + } + } + + /** + * Check if it is possible to ask for a next bucket in the enumeration. + * @return true if there is at least one more bucket. + */ + public boolean hasNextBucket() { + if (mSummary != null) { + return mEnumerationIndex < mSummary.size(); + } else if (mHistory != null) { + return mEnumerationIndex < mHistory.size() + || hasNextUid(); + } + return false; + } + + /** + * Closes the enumeration. Call this method before this object gets out of scope. + */ + @Override + public void close() { + if (mSession != null) { + try { + mSession.close(); + } catch (RemoteException e) { + Log.w(TAG, e); + // Otherwise, meh + } + } + mSession = null; + if (mCloseGuard != null) { + mCloseGuard.close(); + } + } + + // -------------------------END OF PUBLIC API----------------------------------- + + /** + * Collects device summary results into a Bucket. + * @throws RemoteException + */ + Bucket getDeviceSummaryForNetwork() throws RemoteException { + mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp); + + // Setting enumeration index beyond end to avoid accidental enumeration over data that does + // not belong to the calling user. + mEnumerationIndex = mSummary.size(); + + return getSummaryAggregate(); + } + + /** + * Collects summary results and sets summary enumeration mode. + * @throws RemoteException + */ + void startSummaryEnumeration() throws RemoteException { + mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp, + false /* includeTags */); + mEnumerationIndex = 0; + } + + /** + * Collects history results for uid and resets history enumeration index. + */ + void startHistoryEnumeration(int uid, int tag, int state) { + mHistory = null; + try { + mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, + Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL, + mStartTimeStamp, mEndTimeStamp); + setSingleUidTagState(uid, tag, state); + } catch (RemoteException e) { + Log.w(TAG, e); + // Leaving mHistory null + } + mEnumerationIndex = 0; + } + + /** + * Starts uid enumeration for current user. + * @throws RemoteException + */ + void startUserUidEnumeration() throws RemoteException { + // TODO: getRelevantUids should be sensitive to time interval. When that's done, + // the filtering logic below can be removed. + int[] uids = mSession.getRelevantUids(); + // Filtering of uids with empty history. + IntArray filteredUids = new IntArray(uids.length); + for (int uid : uids) { + try { + NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid, + android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, + NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); + if (history != null && history.size() > 0) { + filteredUids.add(uid); + } + } catch (RemoteException e) { + Log.w(TAG, "Error while getting history of uid " + uid, e); + } + } + mUids = filteredUids.toArray(); + mUidOrUidIndex = -1; + stepHistory(); + } + + /** + * Steps to next uid in enumeration and collects history for that. + */ + private void stepHistory(){ + if (hasNextUid()) { + stepUid(); + mHistory = null; + try { + mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(), + android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, + NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); + } catch (RemoteException e) { + Log.w(TAG, e); + // Leaving mHistory null + } + mEnumerationIndex = 0; + } + } + + private void fillBucketFromSummaryEntry(Bucket bucketOut) { + bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); + bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag); + bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); + bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus( + mRecycledSummaryEntry.defaultNetwork); + bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered); + bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming); + bucketOut.mBeginTimeStamp = mStartTimeStamp; + bucketOut.mEndTimeStamp = mEndTimeStamp; + bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes; + bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets; + bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes; + bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets; + } + + /** + * Getting the next item in summary enumeration. + * @param bucketOut Next item will be set here. + * @return true if a next item could be set. + */ + private boolean getNextSummaryBucket(Bucket bucketOut) { + if (bucketOut != null && mEnumerationIndex < mSummary.size()) { + mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry); + fillBucketFromSummaryEntry(bucketOut); + return true; + } + return false; + } + + Bucket getSummaryAggregate() { + if (mSummary == null) { + return null; + } + Bucket bucket = new Bucket(); + if (mRecycledSummaryEntry == null) { + mRecycledSummaryEntry = new android.net.NetworkStats.Entry(); + } + mSummary.getTotal(mRecycledSummaryEntry); + fillBucketFromSummaryEntry(bucket); + return bucket; + } + + /** + * Getting the next item in a history enumeration. + * @param bucketOut Next item will be set here. + * @return true if a next item could be set. + */ + private boolean getNextHistoryBucket(Bucket bucketOut) { + if (bucketOut != null && mHistory != null) { + if (mEnumerationIndex < mHistory.size()) { + mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++, + mRecycledHistoryEntry); + bucketOut.mUid = Bucket.convertUid(getUid()); + bucketOut.mTag = Bucket.convertTag(mTag); + bucketOut.mState = mState; + bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL; + bucketOut.mMetered = Bucket.METERED_ALL; + bucketOut.mRoaming = Bucket.ROAMING_ALL; + bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; + bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart + + mRecycledHistoryEntry.bucketDuration; + bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes; + bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets; + bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes; + bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets; + return true; + } else if (hasNextUid()) { + stepHistory(); + return getNextHistoryBucket(bucketOut); + } + } + return false; + } + + // ------------------ UID LOGIC------------------------ + + private boolean isUidEnumeration() { + return mUids != null; + } + + private boolean hasNextUid() { + return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length; + } + + private int getUid() { + // Check if uid enumeration. + if (isUidEnumeration()) { + if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) { + throw new IndexOutOfBoundsException( + "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length); + } + return mUids[mUidOrUidIndex]; + } + // Single uid mode. + return mUidOrUidIndex; + } + + private void setSingleUidTagState(int uid, int tag, int state) { + mUidOrUidIndex = uid; + mTag = tag; + mState = state; + } + + private void stepUid() { + if (mUids != null) { + ++mUidOrUidIndex; + } + } +} diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java new file mode 100644 index 0000000000..8a6c85d548 --- /dev/null +++ b/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -0,0 +1,767 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * 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 android.app.usage; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.WorkerThread; +import android.app.usage.NetworkStats.Bucket; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.DataUsageRequest; +import android.net.INetworkStatsService; +import android.net.Network; +import android.net.NetworkStack; +import android.net.NetworkStateSnapshot; +import android.net.NetworkTemplate; +import android.net.UnderlyingNetworkInfo; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.net.netstats.provider.NetworkStatsProvider; +import android.os.Binder; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.DataUnit; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.NetworkIdentityUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Provides access to network usage history and statistics. Usage data is collected in + * discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details. + * <p /> + * Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and + * Long.MAX_VALUE can be used to simulate open ended intervals). By default, apps can only obtain + * data about themselves. See the below note for special cases in which apps can obtain data about + * other applications. + * <h3> + * Summary queries + * </h3> + * {@link #querySummaryForDevice} <p /> + * {@link #querySummaryForUser} <p /> + * {@link #querySummary} <p /> + * These queries aggregate network usage across the whole interval. Therefore there will be only one + * bucket for a particular key, state, metered and roaming combination. In case of the user-wide + * and device-wide summaries a single bucket containing the totalised network usage is returned. + * <h3> + * History queries + * </h3> + * {@link #queryDetailsForUid} <p /> + * {@link #queryDetails} <p /> + * These queries do not aggregate over time but do aggregate over state, metered and roaming. + * Therefore there can be multiple buckets for a particular key. However, all Buckets will have + * {@code state} {@link NetworkStats.Bucket#STATE_ALL}, + * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * {@code metered } {@link NetworkStats.Bucket#METERED_ALL}, + * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p /> + * <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the + * calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, + * which is a system-level permission and will not be granted to third-party apps. However, + * declaring the permission implies intention to use the API and the user of the device can grant + * permission through the Settings application. + * <p /> + * Profile owner apps are automatically granted permission to query data on the profile they manage + * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps and carrier- + * privileged apps likewise get access to usage data for all users on the device. + * <p /> + * In addition to tethering usage, usage by removed users and apps, and usage by the system + * is also included in the results for callers with one of these higher levels of access. + * <p /> + * <b>NOTE:</b> Prior to API level {@value android.os.Build.VERSION_CODES#N}, all calls to these APIs required + * the above permission, even to access an app's own data usage, and carrier-privileged apps were + * not included. + */ +@SystemService(Context.NETWORK_STATS_SERVICE) +public class NetworkStatsManager { + private static final String TAG = "NetworkStatsManager"; + private static final boolean DBG = false; + + /** @hide */ + public static final int CALLBACK_LIMIT_REACHED = 0; + /** @hide */ + public static final int CALLBACK_RELEASED = 1; + + /** + * Minimum data usage threshold for registering usage callbacks. + * + * Requests registered with a threshold lower than this will only be triggered once this minimum + * is reached. + * @hide + */ + public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2); + + private final Context mContext; + private final INetworkStatsService mService; + + /** @hide */ + public static final int FLAG_POLL_ON_OPEN = 1 << 0; + /** @hide */ + public static final int FLAG_POLL_FORCE = 1 << 1; + /** @hide */ + public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 2; + + private int mFlags; + + /** + * {@hide} + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkStatsManager(Context context) throws ServiceNotFoundException { + this(context, INetworkStatsService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE))); + } + + /** @hide */ + @VisibleForTesting + public NetworkStatsManager(Context context, INetworkStatsService service) { + mContext = context; + mService = service; + setPollOnOpen(true); + } + + /** @hide */ + public void setPollOnOpen(boolean pollOnOpen) { + if (pollOnOpen) { + mFlags |= FLAG_POLL_ON_OPEN; + } else { + mFlags &= ~FLAG_POLL_ON_OPEN; + } + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @TestApi + public void setPollForce(boolean pollForce) { + if (pollForce) { + mFlags |= FLAG_POLL_FORCE; + } else { + mFlags &= ~FLAG_POLL_FORCE; + } + } + + /** @hide */ + public void setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan) { + if (augmentWithSubscriptionPlan) { + mFlags |= FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN; + } else { + mFlags &= ~FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN; + } + } + + /** @hide */ + public Bucket querySummaryForDevice(NetworkTemplate template, + long startTime, long endTime) throws SecurityException, RemoteException { + Bucket bucket = null; + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, + mService); + bucket = stats.getDeviceSummaryForNetwork(); + + stats.close(); + return bucket; + } + + /** + * Query network usage statistics summaries. Result is summarised data usage for the whole + * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and + * roaming. This means the bucket's start and end timestamp are going to be the same as the + * 'startTime' and 'endTime' parameters. State is going to be + * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL}, + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered {@link NetworkStats.Bucket#METERED_ALL}, + * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Bucket object or null if permissions are insufficient or error happened during + * statistics collection. + */ + @WorkerThread + public Bucket querySummaryForDevice(int networkType, String subscriberId, + long startTime, long endTime) throws SecurityException, RemoteException { + NetworkTemplate template; + try { + template = createTemplate(networkType, subscriberId); + } catch (IllegalArgumentException e) { + if (DBG) Log.e(TAG, "Cannot create template", e); + return null; + } + + return querySummaryForDevice(template, startTime, endTime); + } + + /** + * Query network usage statistics summaries. Result is summarised data usage for all uids + * belonging to calling user. Result is a single Bucket aggregated over time, state and uid. + * This means the bucket's start and end timestamp are going to be the same as the 'startTime' + * and 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, + * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE}, + * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming + * {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Bucket object or null if permissions are insufficient or error happened during + * statistics collection. + */ + @WorkerThread + public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template; + try { + template = createTemplate(networkType, subscriberId); + } catch (IllegalArgumentException e) { + if (DBG) Log.e(TAG, "Cannot create template", e); + return null; + } + + NetworkStats stats; + stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + stats.startSummaryEnumeration(); + + stats.close(); + return stats.getSummaryAggregate(); + } + + /** + * Query network usage statistics summaries. Result filtered to include only uids belonging to + * calling user. Result is aggregated over time, hence all buckets will have the same start and + * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This + * means buckets' start and end timestamps are going to be the same as the 'startTime' and + * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to + * be the same. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics object or null if permissions are insufficient or error happened during + * statistics collection. + */ + @WorkerThread + public NetworkStats querySummary(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template; + try { + template = createTemplate(networkType, subscriberId); + } catch (IllegalArgumentException e) { + if (DBG) Log.e(TAG, "Cannot create template", e); + return null; + } + + return querySummary(template, startTime, endTime); + } + + /** @hide */ + public NetworkStats querySummary(NetworkTemplate template, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkStats result; + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startSummaryEnumeration(); + + return result; + } + + /** + * Query network usage statistics details for a given uid. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) + */ + @WorkerThread + public NetworkStats queryDetailsForUid(int networkType, String subscriberId, + long startTime, long endTime, int uid) throws SecurityException { + return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, + NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL); + } + + /** @hide */ + public NetworkStats queryDetailsForUid(NetworkTemplate template, + long startTime, long endTime, int uid) throws SecurityException { + return queryDetailsForUidTagState(template, startTime, endTime, uid, + NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL); + } + + /** + * Query network usage statistics details for a given uid and tag. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) + */ + @WorkerThread + public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, + long startTime, long endTime, int uid, int tag) throws SecurityException { + return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, + tag, NetworkStats.Bucket.STATE_ALL); + } + + /** + * Query network usage statistics details for a given uid, tag, and state. Only usable for uids + * belonging to calling user. Result is not aggregated over time. This means buckets' start and + * end timestamps are going to be between 'startTime' and 'endTime' parameters. The uid is going + * to be the same as the 'uid' parameter, the tag the same as the 'tag' parameter, and the state + * the same as the 'state' parameter. + * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and + * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param uid UID of app + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags. + * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate + * traffic from all states. + * @return Statistics object or null if an error happened during statistics collection. + * @throws SecurityException if permissions are insufficient to read network statistics. + */ + @WorkerThread + public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId, + long startTime, long endTime, int uid, int tag, int state) throws SecurityException { + NetworkTemplate template; + template = createTemplate(networkType, subscriberId); + + return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state); + } + + /** @hide */ + public NetworkStats queryDetailsForUidTagState(NetworkTemplate template, + long startTime, long endTime, int uid, int tag, int state) throws SecurityException { + + NetworkStats result; + try { + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryEnumeration(uid, tag, state); + } catch (RemoteException e) { + Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag + + " state=" + state, e); + return null; + } + + return result; + } + + /** + * Query network usage statistics details. Result filtered to include only uids belonging to + * calling user. Result is aggregated over state but not aggregated over time, uid, tag, + * metered, nor roaming. This means buckets' start and end timestamps are going to be between + * 'startTime' and 'endTime' parameters. State is going to be + * {@link NetworkStats.Bucket#STATE_ALL}, uid will vary, + * tag {@link NetworkStats.Bucket#TAG_NONE}, + * default network is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, + * and roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param networkType As defined in {@link ConnectivityManager}, e.g. + * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} + * etc. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param startTime Start of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period. Defined in terms of "Unix time", see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics object or null if permissions are insufficient or error happened during + * statistics collection. + */ + @WorkerThread + public NetworkStats queryDetails(int networkType, String subscriberId, long startTime, + long endTime) throws SecurityException, RemoteException { + NetworkTemplate template; + try { + template = createTemplate(networkType, subscriberId); + } catch (IllegalArgumentException e) { + if (DBG) Log.e(TAG, "Cannot create template", e); + return null; + } + + NetworkStats result; + result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startUserUidEnumeration(); + return result; + } + + /** @hide */ + public void registerUsageCallback(NetworkTemplate template, int networkType, + long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { + Objects.requireNonNull(callback, "UsageCallback cannot be null"); + + final Looper looper; + if (handler == null) { + looper = Looper.myLooper(); + } else { + looper = handler.getLooper(); + } + + DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, + template, thresholdBytes); + try { + CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, + template.getSubscriberId(), callback); + callback.request = mService.registerUsageCallback( + mContext.getOpPackageName(), request, new Messenger(callbackHandler), + new Binder()); + if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); + + if (callback.request == null) { + Log.e(TAG, "Request from callback is null; should not happen"); + } + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when registering callback"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers to receive notifications about data usage on specified networks. + * + * @see #registerUsageCallback(int, String, long, UsageCallback, Handler) + */ + public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, + UsageCallback callback) { + registerUsageCallback(networkType, subscriberId, thresholdBytes, callback, + null /* handler */); + } + + /** + * Registers to receive notifications about data usage on specified networks. + * + * <p>The callbacks will continue to be called as long as the process is live or + * {@link #unregisterUsageCallback} is called. + * + * @param networkType Type of network to monitor. Either + {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}. + * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when registering for the mobile network type to receive + * notifications for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. + * <p>Starting with API level 31, calling apps can provide a + * {@code subscriberId} with wifi network type to receive usage for + * wifi networks which is under the given subscription if applicable. + * Otherwise, pass {@code null} when querying all wifi networks. + * @param thresholdBytes Threshold in bytes to be notified on. + * @param callback The {@link UsageCallback} that the system will call when data usage + * has exceeded the specified threshold. + * @param handler to dispatch callback events through, otherwise if {@code null} it uses + * the calling thread. + */ + public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, + UsageCallback callback, @Nullable Handler handler) { + NetworkTemplate template = createTemplate(networkType, subscriberId); + if (DBG) { + Log.d(TAG, "registerUsageCallback called with: {" + + " networkType=" + networkType + + " subscriberId=" + subscriberId + + " thresholdBytes=" + thresholdBytes + + " }"); + } + registerUsageCallback(template, networkType, thresholdBytes, callback, handler); + } + + /** + * Unregisters callbacks on data usage. + * + * @param callback The {@link UsageCallback} used when registering. + */ + public void unregisterUsageCallback(UsageCallback callback) { + if (callback == null || callback.request == null + || callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) { + throw new IllegalArgumentException("Invalid UsageCallback"); + } + try { + mService.unregisterUsageRequest(callback.request); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when unregistering callback"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Base class for usage callbacks. Should be extended by applications wanting notifications. + */ + public static abstract class UsageCallback { + + /** + * Called when data usage has reached the given threshold. + */ + public abstract void onThresholdReached(int networkType, String subscriberId); + + /** + * @hide used for internal bookkeeping + */ + private DataUsageRequest request; + } + + /** + * Registers a custom provider of {@link android.net.NetworkStats} to provide network statistics + * to the system. To unregister, invoke {@link #unregisterNetworkStatsProvider}. + * Note that no de-duplication of statistics between providers is performed, so each provider + * must only report network traffic that is not being reported by any other provider. Also note + * that the provider cannot be re-registered after unregistering. + * + * @param tag a human readable identifier of the custom network stats provider. This is only + * used for debugging. + * @param provider the subclass of {@link NetworkStatsProvider} that needs to be + * registered to the system. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_STATS_PROVIDER, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + @NonNull public void registerNetworkStatsProvider( + @NonNull String tag, + @NonNull NetworkStatsProvider provider) { + try { + if (provider.getProviderCallbackBinder() != null) { + throw new IllegalArgumentException("provider is already registered"); + } + final INetworkStatsProviderCallback cbBinder = + mService.registerNetworkStatsProvider(tag, provider.getProviderBinder()); + provider.setProviderCallbackBinder(cbBinder); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Unregisters an instance of {@link NetworkStatsProvider}. + * + * @param provider the subclass of {@link NetworkStatsProvider} that needs to be + * unregistered to the system. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_STATS_PROVIDER, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + @NonNull public void unregisterNetworkStatsProvider(@NonNull NetworkStatsProvider provider) { + try { + provider.getProviderCallbackBinderOrThrow().unregister(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + private static NetworkTemplate createTemplate(int networkType, String subscriberId) { + final NetworkTemplate template; + switch (networkType) { + case ConnectivityManager.TYPE_MOBILE: + template = subscriberId == null + ? NetworkTemplate.buildTemplateMobileWildcard() + : NetworkTemplate.buildTemplateMobileAll(subscriberId); + break; + case ConnectivityManager.TYPE_WIFI: + template = TextUtils.isEmpty(subscriberId) + ? NetworkTemplate.buildTemplateWifiWildcard() + : NetworkTemplate.buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL, + subscriberId); + break; + default: + throw new IllegalArgumentException("Cannot create template for network type " + + networkType + ", subscriberId '" + + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "'."); + } + return template; + } + + /** + * Notify {@code NetworkStatsService} about network status changed. + * + * Notifies NetworkStatsService of network state changes for data usage accounting purposes. + * + * To avoid races that attribute data usage to wrong network, such as new network with + * the same interface after SIM hot-swap, this function will not return until + * {@code NetworkStatsService} finishes its work of retrieving traffic statistics from + * all data sources. + * + * @param defaultNetworks the list of all networks that could be used by network traffic that + * does not explicitly select a network. + * @param networkStateSnapshots a list of {@link NetworkStateSnapshot}s, one for + * each network that is currently connected. + * @param activeIface the active (i.e., connected) default network interface for the calling + * uid. Used to determine on which network future calls to + * {@link android.net.TrafficStats#incrementOperationCount} applies to. + * @param underlyingNetworkInfos the list of underlying network information for all + * currently-connected VPNs. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void notifyNetworkStatus( + @NonNull List<Network> defaultNetworks, + @NonNull List<NetworkStateSnapshot> networkStateSnapshots, + @Nullable String activeIface, + @NonNull List<UnderlyingNetworkInfo> underlyingNetworkInfos) { + try { + Objects.requireNonNull(defaultNetworks); + Objects.requireNonNull(networkStateSnapshots); + Objects.requireNonNull(underlyingNetworkInfos); + mService.notifyNetworkStatus(defaultNetworks.toArray(new Network[0]), + networkStateSnapshots.toArray(new NetworkStateSnapshot[0]), activeIface, + underlyingNetworkInfos.toArray(new UnderlyingNetworkInfo[0])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class CallbackHandler extends Handler { + private final int mNetworkType; + private final String mSubscriberId; + private UsageCallback mCallback; + + CallbackHandler(Looper looper, int networkType, String subscriberId, + UsageCallback callback) { + super(looper); + mNetworkType = networkType; + mSubscriberId = subscriberId; + mCallback = callback; + } + + @Override + public void handleMessage(Message message) { + DataUsageRequest request = + (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY); + + switch (message.what) { + case CALLBACK_LIMIT_REACHED: { + if (mCallback != null) { + mCallback.onThresholdReached(mNetworkType, mSubscriberId); + } else { + Log.e(TAG, "limit reached with released callback for " + request); + } + break; + } + case CALLBACK_RELEASED: { + if (DBG) Log.d(TAG, "callback released for " + request); + mCallback = null; + break; + } + } + } + + private static Object getObject(Message msg, String key) { + return msg.getData().getParcelable(key); + } + } +} diff --git a/framework-t/src/android/net/DataUsageRequest.aidl b/framework-t/src/android/net/DataUsageRequest.aidl new file mode 100644 index 0000000000..d1937c7b8c --- /dev/null +++ b/framework-t/src/android/net/DataUsageRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * 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 android.net; + +parcelable DataUsageRequest; diff --git a/framework-t/src/android/net/DataUsageRequest.java b/framework-t/src/android/net/DataUsageRequest.java new file mode 100644 index 0000000000..b06d515b3a --- /dev/null +++ b/framework-t/src/android/net/DataUsageRequest.java @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * 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 android.net; + +import android.annotation.Nullable; +import android.net.NetworkTemplate; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Defines a request to register a callbacks. Used to be notified on data usage via + * {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}. + * If no {@code uid}s are set, callbacks are restricted to device-owners, + * carrier-privileged apps, or system apps. + * + * @hide + */ +public final class DataUsageRequest implements Parcelable { + + public static final String PARCELABLE_KEY = "DataUsageRequest"; + public static final int REQUEST_ID_UNSET = 0; + + /** + * Identifies the request. {@link DataUsageRequest}s should only be constructed by + * the Framework and it is used internally to identify the request. + */ + public final int requestId; + + /** + * {@link NetworkTemplate} describing the network to monitor. + */ + public final NetworkTemplate template; + + /** + * Threshold in bytes to be notified on. + */ + public final long thresholdInBytes; + + public DataUsageRequest(int requestId, NetworkTemplate template, long thresholdInBytes) { + this.requestId = requestId; + this.template = template; + this.thresholdInBytes = thresholdInBytes; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(requestId); + dest.writeParcelable(template, flags); + dest.writeLong(thresholdInBytes); + } + + public static final @android.annotation.NonNull Creator<DataUsageRequest> CREATOR = + new Creator<DataUsageRequest>() { + @Override + public DataUsageRequest createFromParcel(Parcel in) { + int requestId = in.readInt(); + NetworkTemplate template = in.readParcelable(null); + long thresholdInBytes = in.readLong(); + DataUsageRequest result = new DataUsageRequest(requestId, template, + thresholdInBytes); + return result; + } + + @Override + public DataUsageRequest[] newArray(int size) { + return new DataUsageRequest[size]; + } + }; + + @Override + public String toString() { + return "DataUsageRequest [ requestId=" + requestId + + ", networkTemplate=" + template + + ", thresholdInBytes=" + thresholdInBytes + " ]"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof DataUsageRequest == false) return false; + DataUsageRequest that = (DataUsageRequest) obj; + return that.requestId == this.requestId + && Objects.equals(that.template, this.template) + && that.thresholdInBytes == this.thresholdInBytes; + } + + @Override + public int hashCode() { + return Objects.hash(requestId, template, thresholdInBytes); + } + +} diff --git a/framework-t/src/android/net/INetworkStatsService.aidl b/framework-t/src/android/net/INetworkStatsService.aidl new file mode 100644 index 0000000000..12937b5cb2 --- /dev/null +++ b/framework-t/src/android/net/INetworkStatsService.aidl @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import android.net.DataUsageRequest; +import android.net.INetworkStatsSession; +import android.net.Network; +import android.net.NetworkStateSnapshot; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.UnderlyingNetworkInfo; +import android.net.netstats.provider.INetworkStatsProvider; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.os.IBinder; +import android.os.Messenger; + +/** {@hide} */ +interface INetworkStatsService { + + /** Start a statistics query session. */ + @UnsupportedAppUsage + INetworkStatsSession openSession(); + + /** Start a statistics query session. If calling package is profile or device owner then it is + * granted automatic access if apiLevel is NetworkStatsManager.API_LEVEL_DPC_ALLOWED. If + * apiLevel is at least NetworkStatsManager.API_LEVEL_REQUIRES_PACKAGE_USAGE_STATS then + * PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted + * READ_NETWORK_USAGE_STATS is checked for. + */ + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage); + + /** Return data layer snapshot of UID network usage. */ + @UnsupportedAppUsage + NetworkStats getDataLayerSnapshotForUid(int uid); + + /** Get a detailed snapshot of stats since boot for all UIDs. + * + * <p>Results will not always be limited to stats on requiredIfaces when specified: stats for + * interfaces stacked on the specified interfaces, or for interfaces on which the specified + * interfaces are stacked on, will also be included. + * @param requiredIfaces Interface names to get data for, or {@link NetworkStats#INTERFACES_ALL}. + */ + NetworkStats getDetailedUidStats(in String[] requiredIfaces); + + /** Return set of any ifaces associated with mobile networks since boot. */ + @UnsupportedAppUsage + String[] getMobileIfaces(); + + /** Increment data layer count of operations performed for UID and tag. */ + void incrementOperationCount(int uid, int tag, int operationCount); + + /** Notify {@code NetworkStatsService} about network status changed. */ + void notifyNetworkStatus( + in Network[] defaultNetworks, + in NetworkStateSnapshot[] snapshots, + in String activeIface, + in UnderlyingNetworkInfo[] underlyingNetworkInfos); + /** Force update of statistics. */ + @UnsupportedAppUsage + void forceUpdate(); + + /** Registers a callback on data usage. */ + DataUsageRequest registerUsageCallback(String callingPackage, + in DataUsageRequest request, in Messenger messenger, in IBinder binder); + + /** Unregisters a callback on data usage. */ + void unregisterUsageRequest(in DataUsageRequest request); + + /** Get the uid stats information since boot */ + long getUidStats(int uid, int type); + + /** Get the iface stats information since boot */ + long getIfaceStats(String iface, int type); + + /** Get the total network stats information since boot */ + long getTotalStats(int type); + + /** Registers a network stats provider */ + INetworkStatsProviderCallback registerNetworkStatsProvider(String tag, + in INetworkStatsProvider provider); +} diff --git a/framework-t/src/android/net/INetworkStatsSession.aidl b/framework-t/src/android/net/INetworkStatsSession.aidl new file mode 100644 index 0000000000..dfedf6633d --- /dev/null +++ b/framework-t/src/android/net/INetworkStatsSession.aidl @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.net; + +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; + +/** {@hide} */ +interface INetworkStatsSession { + + /** Return device aggregated network layer usage summary for traffic that matches template. */ + NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end); + + /** Return network layer usage summary for traffic that matches template. */ + @UnsupportedAppUsage + NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); + /** Return historical network layer stats for traffic that matches template. */ + @UnsupportedAppUsage + NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); + + /** + * Return network layer usage summary per UID for traffic that matches template. + * + * <p>The resulting {@code NetworkStats#getElapsedRealtime()} contains time delta between + * {@code start} and {@code end}. + * + * @param template - a predicate to filter netstats. + * @param start - start of the range, timestamp in milliseconds since the epoch. + * @param end - end of the range, timestamp in milliseconds since the epoch. + * @param includeTags - includes data usage tags if true. + */ + @UnsupportedAppUsage + NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + /** Return historical network layer stats for specific UID traffic that matches template. */ + @UnsupportedAppUsage + NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); + /** Return historical network layer stats for specific UID traffic that matches template. */ + NetworkStatsHistory getHistoryIntervalForUid(in NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end); + + /** Return array of uids that have stats and are accessible to the calling user */ + int[] getRelevantUids(); + + @UnsupportedAppUsage + void close(); + +} diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java new file mode 100644 index 0000000000..eb8f43e3d0 --- /dev/null +++ b/framework-t/src/android/net/NetworkIdentity.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import static android.net.ConnectivityManager.TYPE_WIFI; + +import android.annotation.Nullable; +import android.content.Context; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.service.NetworkIdentityProto; +import android.telephony.Annotation.NetworkType; +import android.util.proto.ProtoOutputStream; + +import com.android.net.module.util.NetworkCapabilitiesUtils; +import com.android.net.module.util.NetworkIdentityUtils; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Network definition that includes strong identity. Analogous to combining + * {@link NetworkCapabilities} and an IMSI. + * + * @hide + */ +public class NetworkIdentity implements Comparable<NetworkIdentity> { + private static final String TAG = "NetworkIdentity"; + + public static final int SUBTYPE_COMBINED = -1; + + /** + * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}. + * @hide + */ + public static final int OEM_NONE = 0x0; + /** + * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}. + * @hide + */ + public static final int OEM_PAID = 0x1; + /** + * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}. + * @hide + */ + public static final int OEM_PRIVATE = 0x2; + + final int mType; + final int mSubType; + final String mSubscriberId; + final String mNetworkId; + final boolean mRoaming; + final boolean mMetered; + final boolean mDefaultNetwork; + final int mOemManaged; + + public NetworkIdentity( + int type, int subType, String subscriberId, String networkId, boolean roaming, + boolean metered, boolean defaultNetwork, int oemManaged) { + mType = type; + mSubType = subType; + mSubscriberId = subscriberId; + mNetworkId = networkId; + mRoaming = roaming; + mMetered = metered; + mDefaultNetwork = defaultNetwork; + mOemManaged = oemManaged; + } + + @Override + public int hashCode() { + return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered, + mDefaultNetwork, mOemManaged); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof NetworkIdentity) { + final NetworkIdentity ident = (NetworkIdentity) obj; + return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming + && Objects.equals(mSubscriberId, ident.mSubscriberId) + && Objects.equals(mNetworkId, ident.mNetworkId) + && mMetered == ident.mMetered + && mDefaultNetwork == ident.mDefaultNetwork + && mOemManaged == ident.mOemManaged; + } + return false; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("{"); + builder.append("type=").append(mType); + builder.append(", subType="); + if (mSubType == SUBTYPE_COMBINED) { + builder.append("COMBINED"); + } else { + builder.append(mSubType); + } + if (mSubscriberId != null) { + builder.append(", subscriberId=") + .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); + } + if (mNetworkId != null) { + builder.append(", networkId=").append(mNetworkId); + } + if (mRoaming) { + builder.append(", ROAMING"); + } + builder.append(", metered=").append(mMetered); + builder.append(", defaultNetwork=").append(mDefaultNetwork); + builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); + return builder.append("}").toString(); + } + + /** + * Get the human readable representation of a bitfield representing the OEM managed state of a + * network. + */ + static String getOemManagedNames(int oemManaged) { + if (oemManaged == OEM_NONE) { + return "OEM_NONE"; + } + final int[] bitPositions = NetworkCapabilitiesUtils.unpackBits(oemManaged); + final ArrayList<String> oemManagedNames = new ArrayList<String>(); + for (int position : bitPositions) { + oemManagedNames.add(nameOfOemManaged(1 << position)); + } + return String.join(",", oemManagedNames); + } + + private static String nameOfOemManaged(int oemManagedBit) { + switch (oemManagedBit) { + case OEM_PAID: + return "OEM_PAID"; + case OEM_PRIVATE: + return "OEM_PRIVATE"; + default: + return "Invalid(" + oemManagedBit + ")"; + } + } + + public void dumpDebug(ProtoOutputStream proto, long tag) { + final long start = proto.start(tag); + + proto.write(NetworkIdentityProto.TYPE, mType); + + // Not dumping mSubType, subtypes are no longer supported. + + if (mSubscriberId != null) { + proto.write(NetworkIdentityProto.SUBSCRIBER_ID, + NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); + } + proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId); + proto.write(NetworkIdentityProto.ROAMING, mRoaming); + proto.write(NetworkIdentityProto.METERED, mMetered); + proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); + proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged); + + proto.end(start); + } + + public int getType() { + return mType; + } + + public int getSubType() { + return mSubType; + } + + public String getSubscriberId() { + return mSubscriberId; + } + + public String getNetworkId() { + return mNetworkId; + } + + public boolean getRoaming() { + return mRoaming; + } + + public boolean getMetered() { + return mMetered; + } + + public boolean getDefaultNetwork() { + return mDefaultNetwork; + } + + public int getOemManaged() { + return mOemManaged; + } + + /** + * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and + * {@code subType}, assuming that any mobile networks are using the current IMSI. + * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* + * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + */ + public static NetworkIdentity buildNetworkIdentity(Context context, + NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) { + final int legacyType = snapshot.getLegacyType(); + + final String subscriberId = snapshot.getSubscriberId(); + String networkId = null; + boolean roaming = !snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + boolean metered = !(snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + || snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + + final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities()); + + if (legacyType == TYPE_WIFI) { + networkId = snapshot.getNetworkCapabilities().getSsid(); + if (networkId == null) { + final WifiManager wifi = context.getSystemService(WifiManager.class); + final WifiInfo info = wifi.getConnectionInfo(); + networkId = info != null ? info.getSSID() : null; + } + } + + return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered, + defaultNetwork, oemManaged); + } + + /** + * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}. + * @hide + */ + public static int getOemBitfield(NetworkCapabilities nc) { + int oemManaged = OEM_NONE; + + if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) { + oemManaged |= OEM_PAID; + } + if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) { + oemManaged |= OEM_PRIVATE; + } + + return oemManaged; + } + + @Override + public int compareTo(NetworkIdentity another) { + int res = Integer.compare(mType, another.mType); + if (res == 0) { + res = Integer.compare(mSubType, another.mSubType); + } + if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { + res = mSubscriberId.compareTo(another.mSubscriberId); + } + if (res == 0 && mNetworkId != null && another.mNetworkId != null) { + res = mNetworkId.compareTo(another.mNetworkId); + } + if (res == 0) { + res = Boolean.compare(mRoaming, another.mRoaming); + } + if (res == 0) { + res = Boolean.compare(mMetered, another.mMetered); + } + if (res == 0) { + res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork); + } + if (res == 0) { + res = Integer.compare(mOemManaged, another.mOemManaged); + } + return res; + } +} diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java new file mode 100644 index 0000000000..abbebef85c --- /dev/null +++ b/framework-t/src/android/net/NetworkIdentitySet.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import static android.net.ConnectivityManager.TYPE_MOBILE; + +import android.service.NetworkIdentitySetProto; +import android.util.proto.ProtoOutputStream; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.HashSet; + +/** + * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} + * active on that interface. + * + * @hide + */ +public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements + Comparable<NetworkIdentitySet> { + private static final int VERSION_INIT = 1; + private static final int VERSION_ADD_ROAMING = 2; + private static final int VERSION_ADD_NETWORK_ID = 3; + private static final int VERSION_ADD_METERED = 4; + private static final int VERSION_ADD_DEFAULT_NETWORK = 5; + private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; + + public NetworkIdentitySet() { + } + + public NetworkIdentitySet(DataInput in) throws IOException { + final int version = in.readInt(); + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + if (version <= VERSION_INIT) { + final int ignored = in.readInt(); + } + final int type = in.readInt(); + final int subType = in.readInt(); + final String subscriberId = readOptionalString(in); + final String networkId; + if (version >= VERSION_ADD_NETWORK_ID) { + networkId = readOptionalString(in); + } else { + networkId = null; + } + final boolean roaming; + if (version >= VERSION_ADD_ROAMING) { + roaming = in.readBoolean(); + } else { + roaming = false; + } + + final boolean metered; + if (version >= VERSION_ADD_METERED) { + metered = in.readBoolean(); + } else { + // If this is the old data and the type is mobile, treat it as metered. (Note that + // if this is a mobile network, TYPE_MOBILE is the only possible type that could be + // used.) + metered = (type == TYPE_MOBILE); + } + + final boolean defaultNetwork; + if (version >= VERSION_ADD_DEFAULT_NETWORK) { + defaultNetwork = in.readBoolean(); + } else { + defaultNetwork = true; + } + + final int oemNetCapabilities; + if (version >= VERSION_ADD_OEM_MANAGED_NETWORK) { + oemNetCapabilities = in.readInt(); + } else { + oemNetCapabilities = NetworkIdentity.OEM_NONE; + } + + add(new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered, + defaultNetwork, oemNetCapabilities)); + } + } + + /** + * Method to serialize this object into a {@code DataOutput}. + */ + public void writeToStream(DataOutput out) throws IOException { + out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK); + out.writeInt(size()); + for (NetworkIdentity ident : this) { + out.writeInt(ident.getType()); + out.writeInt(ident.getSubType()); + writeOptionalString(out, ident.getSubscriberId()); + writeOptionalString(out, ident.getNetworkId()); + out.writeBoolean(ident.getRoaming()); + out.writeBoolean(ident.getMetered()); + out.writeBoolean(ident.getDefaultNetwork()); + out.writeInt(ident.getOemManaged()); + } + } + + /** @return whether any {@link NetworkIdentity} in this set is considered metered. */ + public boolean isAnyMemberMetered() { + if (isEmpty()) { + return false; + } + for (NetworkIdentity ident : this) { + if (ident.getMetered()) { + return true; + } + } + return false; + } + + /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */ + public boolean isAnyMemberRoaming() { + if (isEmpty()) { + return false; + } + for (NetworkIdentity ident : this) { + if (ident.getRoaming()) { + return true; + } + } + return false; + } + + /** @return whether any {@link NetworkIdentity} in this set is considered on the default + network. */ + public boolean areAllMembersOnDefaultNetwork() { + if (isEmpty()) { + return true; + } + for (NetworkIdentity ident : this) { + if (!ident.getDefaultNetwork()) { + return false; + } + } + return true; + } + + private static void writeOptionalString(DataOutput out, String value) throws IOException { + if (value != null) { + out.writeByte(1); + out.writeUTF(value); + } else { + out.writeByte(0); + } + } + + private static String readOptionalString(DataInput in) throws IOException { + if (in.readByte() != 0) { + return in.readUTF(); + } else { + return null; + } + } + + @Override + public int compareTo(NetworkIdentitySet another) { + if (isEmpty()) return -1; + if (another.isEmpty()) return 1; + + final NetworkIdentity ident = iterator().next(); + final NetworkIdentity anotherIdent = another.iterator().next(); + return ident.compareTo(anotherIdent); + } + + /** + * Method to dump this object into proto debug file. + */ + public void dumpDebug(ProtoOutputStream proto, long tag) { + final long start = proto.start(tag); + + for (NetworkIdentity ident : this) { + ident.dumpDebug(proto, NetworkIdentitySetProto.IDENTITIES); + } + + proto.end(start); + } +} diff --git a/framework-t/src/android/net/NetworkStateSnapshot.java b/framework-t/src/android/net/NetworkStateSnapshot.java new file mode 100644 index 0000000000..3915634392 --- /dev/null +++ b/framework-t/src/android/net/NetworkStateSnapshot.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 android.net; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.net.module.util.NetworkIdentityUtils; + +import java.util.Objects; + +/** + * Snapshot of network state. + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +public final class NetworkStateSnapshot implements Parcelable { + /** The network associated with this snapshot. */ + @NonNull + private final Network mNetwork; + + /** The {@link NetworkCapabilities} of the network associated with this snapshot. */ + @NonNull + private final NetworkCapabilities mNetworkCapabilities; + + /** The {@link LinkProperties} of the network associated with this snapshot. */ + @NonNull + private final LinkProperties mLinkProperties; + + /** + * The Subscriber Id of the network associated with this snapshot. See + * {@link android.telephony.TelephonyManager#getSubscriberId()}. + */ + @Nullable + private final String mSubscriberId; + + /** + * The legacy type of the network associated with this snapshot. See + * {@code ConnectivityManager#TYPE_*}. + */ + private final int mLegacyType; + + public NetworkStateSnapshot(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, + @Nullable String subscriberId, int legacyType) { + mNetwork = Objects.requireNonNull(network); + mNetworkCapabilities = Objects.requireNonNull(networkCapabilities); + mLinkProperties = Objects.requireNonNull(linkProperties); + mSubscriberId = subscriberId; + mLegacyType = legacyType; + } + + /** @hide */ + public NetworkStateSnapshot(@NonNull Parcel in) { + mNetwork = in.readParcelable(null); + mNetworkCapabilities = in.readParcelable(null); + mLinkProperties = in.readParcelable(null); + mSubscriberId = in.readString(); + mLegacyType = in.readInt(); + } + + /** Get the network associated with this snapshot */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** Get {@link NetworkCapabilities} of the network associated with this snapshot. */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + /** Get the {@link LinkProperties} of the network associated with this snapshot. */ + @NonNull + public LinkProperties getLinkProperties() { + return mLinkProperties; + } + + /** Get the Subscriber Id of the network associated with this snapshot. */ + @Nullable + public String getSubscriberId() { + return mSubscriberId; + } + + /** + * Get the legacy type of the network associated with this snapshot. + * @return the legacy network type. See {@code ConnectivityManager#TYPE_*}. + */ + public int getLegacyType() { + return mLegacyType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(mNetwork, flags); + out.writeParcelable(mNetworkCapabilities, flags); + out.writeParcelable(mLinkProperties, flags); + out.writeString(mSubscriberId); + out.writeInt(mLegacyType); + } + + @NonNull + public static final Creator<NetworkStateSnapshot> CREATOR = + new Creator<NetworkStateSnapshot>() { + @NonNull + @Override + public NetworkStateSnapshot createFromParcel(@NonNull Parcel in) { + return new NetworkStateSnapshot(in); + } + + @NonNull + @Override + public NetworkStateSnapshot[] newArray(int size) { + return new NetworkStateSnapshot[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NetworkStateSnapshot)) return false; + NetworkStateSnapshot that = (NetworkStateSnapshot) o; + return mLegacyType == that.mLegacyType + && Objects.equals(mNetwork, that.mNetwork) + && Objects.equals(mNetworkCapabilities, that.mNetworkCapabilities) + && Objects.equals(mLinkProperties, that.mLinkProperties) + && Objects.equals(mSubscriberId, that.mSubscriberId); + } + + @Override + public int hashCode() { + return Objects.hash(mNetwork, + mNetworkCapabilities, mLinkProperties, mSubscriberId, mLegacyType); + } + + @Override + public String toString() { + return "NetworkStateSnapshot{" + + "network=" + mNetwork + + ", networkCapabilities=" + mNetworkCapabilities + + ", linkProperties=" + mLinkProperties + + ", subscriberId='" + NetworkIdentityUtils.scrubSubscriberId(mSubscriberId) + '\'' + + ", legacyType=" + mLegacyType + + '}'; + } +} diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java new file mode 100644 index 0000000000..c7ffc19338 --- /dev/null +++ b/framework-t/src/android/net/NetworkStats.java @@ -0,0 +1,1738 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.os.SystemClock; +import android.util.SparseBooleanArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import libcore.util.EmptyArray; + +import java.io.CharArrayWriter; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Collection of active network statistics. Can contain summary details across + * all interfaces, or details with per-UID granularity. Internally stores data + * as a large table, closely matching {@code /proc/} data format. This structure + * optimizes for rapid in-memory comparison, but consider using + * {@link NetworkStatsHistory} when persisting. + * + * @hide + */ +// @NotThreadSafe +@SystemApi +public final class NetworkStats implements Parcelable { + private static final String TAG = "NetworkStats"; + + /** + * {@link #iface} value when interface details unavailable. + * @hide + */ + @Nullable public static final String IFACE_ALL = null; + + /** + * Virtual network interface for video telephony. This is for VT data usage counting + * purpose. + */ + public static final String IFACE_VT = "vt_data0"; + + /** {@link #uid} value when UID details unavailable. */ + public static final int UID_ALL = -1; + /** Special UID value for data usage by tethering. */ + public static final int UID_TETHERING = -5; + + /** + * {@link #tag} value matching any tag. + * @hide + */ + // TODO: Rename TAG_ALL to TAG_ANY. + public static final int TAG_ALL = -1; + /** + * {@link #set} value for all sets combined, not including debug sets. + * @hide + */ + public static final int SET_ALL = -1; + /** {@link #set} value where background data is accounted. */ + public static final int SET_DEFAULT = 0; + /** {@link #set} value where foreground data is accounted. */ + public static final int SET_FOREGROUND = 1; + /** + * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. + * @hide + */ + public static final int SET_DEBUG_START = 1000; + /** + * Debug {@link #set} value when the VPN stats are moved in. + * @hide + */ + public static final int SET_DBG_VPN_IN = 1001; + /** + * Debug {@link #set} value when the VPN stats are moved out of a vpn UID. + * @hide + */ + public static final int SET_DBG_VPN_OUT = 1002; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "SET_" }, value = { + SET_ALL, + SET_DEFAULT, + SET_FOREGROUND, + SET_DEBUG_START, + SET_DBG_VPN_IN, + SET_DBG_VPN_OUT + }) + public @interface State { + } + + /** + * Include all interfaces when filtering + * @hide + */ + public @Nullable static final String[] INTERFACES_ALL = null; + + /** {@link #tag} value for total data across all tags. */ + // TODO: Rename TAG_NONE to TAG_ALL. + public static final int TAG_NONE = 0; + + /** + * {@link #metered} value to account for all metered states. + * @hide + */ + public static final int METERED_ALL = -1; + /** {@link #metered} value where native, unmetered data is accounted. */ + public static final int METERED_NO = 0; + /** {@link #metered} value where metered data is accounted. */ + public static final int METERED_YES = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "METERED_" }, value = { + METERED_ALL, + METERED_NO, + METERED_YES + }) + public @interface Meteredness { + } + + + /** + * {@link #roaming} value to account for all roaming states. + * @hide + */ + public static final int ROAMING_ALL = -1; + /** {@link #roaming} value where native, non-roaming data is accounted. */ + public static final int ROAMING_NO = 0; + /** {@link #roaming} value where roaming data is accounted. */ + public static final int ROAMING_YES = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ROAMING_" }, value = { + ROAMING_ALL, + ROAMING_NO, + ROAMING_YES + }) + public @interface Roaming { + } + + /** + * {@link #onDefaultNetwork} value to account for all default network states. + * @hide + */ + public static final int DEFAULT_NETWORK_ALL = -1; + /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ + public static final int DEFAULT_NETWORK_NO = 0; + /** {@link #onDefaultNetwork} value to account for usage while the default network. */ + public static final int DEFAULT_NETWORK_YES = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { + DEFAULT_NETWORK_ALL, + DEFAULT_NETWORK_NO, + DEFAULT_NETWORK_YES + }) + public @interface DefaultNetwork { + } + + /** + * Denotes a request for stats at the interface level. + * @hide + */ + public static final int STATS_PER_IFACE = 0; + /** + * Denotes a request for stats at the interface and UID level. + * @hide + */ + public static final int STATS_PER_UID = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATS_PER_" }, value = { + STATS_PER_IFACE, + STATS_PER_UID + }) + public @interface StatsType { + } + + private static final String CLATD_INTERFACE_PREFIX = "v4-"; + // Delta between IPv4 header (20b) and IPv6 header (40b). + // Used for correct stats accounting on clatd interfaces. + private static final int IPV4V6_HEADER_DELTA = 20; + + // TODO: move fields to "mVariable" notation + + /** + * {@link SystemClock#elapsedRealtime()} timestamp in milliseconds when this data was + * generated. + * It's a timestamps delta when {@link #subtract()}, + * {@code INetworkStatsSession#getSummaryForAllUid()} methods are used. + */ + private long elapsedRealtime; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private int size; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private int capacity; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private String[] iface; + @UnsupportedAppUsage + private int[] uid; + @UnsupportedAppUsage + private int[] set; + @UnsupportedAppUsage + private int[] tag; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private int[] metered; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private int[] roaming; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private int[] defaultNetwork; + @UnsupportedAppUsage + private long[] rxBytes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private long[] rxPackets; + @UnsupportedAppUsage + private long[] txBytes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private long[] txPackets; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private long[] operations; + + /** + * Basic element of network statistics. Contains the number of packets and number of bytes + * transferred on both directions in a given set of conditions. See + * {@link Entry#Entry(String, int, int, int, int, int, int, long, long, long, long, long)}. + * + * @hide + */ + @SystemApi + public static class Entry { + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public String iface; + /** @hide */ + @UnsupportedAppUsage + public int uid; + /** @hide */ + @UnsupportedAppUsage + public int set; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public int tag; + /** + * Note that this is only populated w/ the default value when read from /proc or written + * to disk. We merge in the correct value when reporting this value to clients of + * getSummary(). + * @hide + */ + public int metered; + /** + * Note that this is only populated w/ the default value when read from /proc or written + * to disk. We merge in the correct value when reporting this value to clients of + * getSummary(). + * @hide + */ + public int roaming; + /** + * Note that this is only populated w/ the default value when read from /proc or written + * to disk. We merge in the correct value when reporting this value to clients of + * getSummary(). + * @hide + */ + public int defaultNetwork; + /** @hide */ + @UnsupportedAppUsage + public long rxBytes; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long rxPackets; + /** @hide */ + @UnsupportedAppUsage + public long txBytes; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long txPackets; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long operations; + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public Entry() { + this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + } + + /** @hide */ + public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, + operations); + } + + /** @hide */ + public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, + rxBytes, rxPackets, txBytes, txPackets, operations); + } + + /** + * Construct a {@link Entry} object by giving statistics of packet and byte transferred on + * both direction, and associated with a set of given conditions. + * + * @param iface interface name of this {@link Entry}. Or null if not specified. + * @param uid uid of this {@link Entry}. {@link #UID_TETHERING} if this {@link Entry} is + * for tethering. Or {@link #UID_ALL} if this {@link NetworkStats} is only + * counting iface stats. + * @param set usage state of this {@link Entry}. Should be one of the following + * values: {@link #SET_DEFAULT}, {@link #SET_FOREGROUND}. + * @param tag tag of this {@link Entry}. + * @param metered metered state of this {@link Entry}. Should be one of the following + * values: {link #METERED_YES}, {link #METERED_NO}. + * @param roaming roaming state of this {@link Entry}. Should be one of the following + * values: {link #ROAMING_YES}, {link #ROAMING_NO}. + * @param defaultNetwork default network status of this {@link Entry}. Should be one + * of the following values: {link #DEFAULT_NETWORK_YES}, + * {link #DEFAULT_NETWORK_NO}. + * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param rxPackets Number of packets received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param operations count of network operations performed for this {@link Entry}. This can + * be used to derive bytes-per-operation. + */ + public Entry(@Nullable String iface, int uid, @State int set, int tag, + @Meteredness int metered, @Roaming int roaming, @DefaultNetwork int defaultNetwork, + long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + this.iface = iface; + this.uid = uid; + this.set = set; + this.tag = tag; + this.metered = metered; + this.roaming = roaming; + this.defaultNetwork = defaultNetwork; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.operations = operations; + } + + /** @hide */ + public boolean isNegative() { + return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; + } + + /** @hide */ + public boolean isEmpty() { + return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 + && operations == 0; + } + + /** @hide */ + public void add(Entry another) { + this.rxBytes += another.rxBytes; + this.rxPackets += another.rxPackets; + this.txBytes += another.txBytes; + this.txPackets += another.txPackets; + this.operations += another.operations; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("iface=").append(iface); + builder.append(" uid=").append(uid); + builder.append(" set=").append(setToString(set)); + builder.append(" tag=").append(tagToString(tag)); + builder.append(" metered=").append(meteredToString(metered)); + builder.append(" roaming=").append(roamingToString(roaming)); + builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork)); + builder.append(" rxBytes=").append(rxBytes); + builder.append(" rxPackets=").append(rxPackets); + builder.append(" txBytes=").append(txBytes); + builder.append(" txPackets=").append(txPackets); + builder.append(" operations=").append(operations); + return builder.toString(); + } + + /** @hide */ + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof Entry) { + final Entry e = (Entry) o; + return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered + && roaming == e.roaming && defaultNetwork == e.defaultNetwork + && rxBytes == e.rxBytes && rxPackets == e.rxPackets + && txBytes == e.txBytes && txPackets == e.txPackets + && operations == e.operations && iface.equals(e.iface); + } + return false; + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); + } + } + + public NetworkStats(long elapsedRealtime, int initialSize) { + this.elapsedRealtime = elapsedRealtime; + this.size = 0; + if (initialSize > 0) { + this.capacity = initialSize; + this.iface = new String[initialSize]; + this.uid = new int[initialSize]; + this.set = new int[initialSize]; + this.tag = new int[initialSize]; + this.metered = new int[initialSize]; + this.roaming = new int[initialSize]; + this.defaultNetwork = new int[initialSize]; + this.rxBytes = new long[initialSize]; + this.rxPackets = new long[initialSize]; + this.txBytes = new long[initialSize]; + this.txPackets = new long[initialSize]; + this.operations = new long[initialSize]; + } else { + // Special case for use by NetworkStatsFactory to start out *really* empty. + clear(); + } + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkStats(Parcel parcel) { + elapsedRealtime = parcel.readLong(); + size = parcel.readInt(); + capacity = parcel.readInt(); + iface = parcel.createStringArray(); + uid = parcel.createIntArray(); + set = parcel.createIntArray(); + tag = parcel.createIntArray(); + metered = parcel.createIntArray(); + roaming = parcel.createIntArray(); + defaultNetwork = parcel.createIntArray(); + rxBytes = parcel.createLongArray(); + rxPackets = parcel.createLongArray(); + txBytes = parcel.createLongArray(); + txPackets = parcel.createLongArray(); + operations = parcel.createLongArray(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(elapsedRealtime); + dest.writeInt(size); + dest.writeInt(capacity); + dest.writeStringArray(iface); + dest.writeIntArray(uid); + dest.writeIntArray(set); + dest.writeIntArray(tag); + dest.writeIntArray(metered); + dest.writeIntArray(roaming); + dest.writeIntArray(defaultNetwork); + dest.writeLongArray(rxBytes); + dest.writeLongArray(rxPackets); + dest.writeLongArray(txBytes); + dest.writeLongArray(txPackets); + dest.writeLongArray(operations); + } + + /** + * @hide + */ + @Override + public NetworkStats clone() { + final NetworkStats clone = new NetworkStats(elapsedRealtime, size); + NetworkStats.Entry entry = null; + for (int i = 0; i < size; i++) { + entry = getValues(i, entry); + clone.insertEntry(entry); + } + return clone; + } + + /** + * Clear all data stored in this object. + * @hide + */ + public void clear() { + this.capacity = 0; + this.iface = EmptyArray.STRING; + this.uid = EmptyArray.INT; + this.set = EmptyArray.INT; + this.tag = EmptyArray.INT; + this.metered = EmptyArray.INT; + this.roaming = EmptyArray.INT; + this.defaultNetwork = EmptyArray.INT; + this.rxBytes = EmptyArray.LONG; + this.rxPackets = EmptyArray.LONG; + this.txBytes = EmptyArray.LONG; + this.txPackets = EmptyArray.LONG; + this.operations = EmptyArray.LONG; + } + + /** @hide */ + @VisibleForTesting + public NetworkStats insertEntry( + String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { + return insertEntry( + iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); + } + + /** @hide */ + @VisibleForTesting + public NetworkStats insertEntry(String iface, int uid, int set, int tag, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + return insertEntry(new Entry( + iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); + } + + /** @hide */ + @VisibleForTesting + public NetworkStats insertEntry(String iface, int uid, int set, int tag, int metered, + int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, + long txPackets, long operations) { + return insertEntry(new Entry( + iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, + txBytes, txPackets, operations)); + } + + /** + * Add new stats entry, copying from given {@link Entry}. The {@link Entry} + * object can be recycled across multiple calls. + * @hide + */ + public NetworkStats insertEntry(Entry entry) { + if (size >= capacity) { + final int newLength = Math.max(size, 10) * 3 / 2; + iface = Arrays.copyOf(iface, newLength); + uid = Arrays.copyOf(uid, newLength); + set = Arrays.copyOf(set, newLength); + tag = Arrays.copyOf(tag, newLength); + metered = Arrays.copyOf(metered, newLength); + roaming = Arrays.copyOf(roaming, newLength); + defaultNetwork = Arrays.copyOf(defaultNetwork, newLength); + rxBytes = Arrays.copyOf(rxBytes, newLength); + rxPackets = Arrays.copyOf(rxPackets, newLength); + txBytes = Arrays.copyOf(txBytes, newLength); + txPackets = Arrays.copyOf(txPackets, newLength); + operations = Arrays.copyOf(operations, newLength); + capacity = newLength; + } + + setValues(size, entry); + size++; + + return this; + } + + private void setValues(int i, Entry entry) { + iface[i] = entry.iface; + uid[i] = entry.uid; + set[i] = entry.set; + tag[i] = entry.tag; + metered[i] = entry.metered; + roaming[i] = entry.roaming; + defaultNetwork[i] = entry.defaultNetwork; + rxBytes[i] = entry.rxBytes; + rxPackets[i] = entry.rxPackets; + txBytes[i] = entry.txBytes; + txPackets[i] = entry.txPackets; + operations[i] = entry.operations; + } + + /** + * Return specific stats entry. + * @hide + */ + @UnsupportedAppUsage + public Entry getValues(int i, Entry recycle) { + final Entry entry = recycle != null ? recycle : new Entry(); + entry.iface = iface[i]; + entry.uid = uid[i]; + entry.set = set[i]; + entry.tag = tag[i]; + entry.metered = metered[i]; + entry.roaming = roaming[i]; + entry.defaultNetwork = defaultNetwork[i]; + entry.rxBytes = rxBytes[i]; + entry.rxPackets = rxPackets[i]; + entry.txBytes = txBytes[i]; + entry.txPackets = txPackets[i]; + entry.operations = operations[i]; + return entry; + } + + /** + * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index + * @{code dest}. + */ + private void maybeCopyEntry(int dest, int src) { + if (dest == src) return; + iface[dest] = iface[src]; + uid[dest] = uid[src]; + set[dest] = set[src]; + tag[dest] = tag[src]; + metered[dest] = metered[src]; + roaming[dest] = roaming[src]; + defaultNetwork[dest] = defaultNetwork[src]; + rxBytes[dest] = rxBytes[src]; + rxPackets[dest] = rxPackets[src]; + txBytes[dest] = txBytes[src]; + txPackets[dest] = txPackets[src]; + operations[dest] = operations[src]; + } + + /** @hide */ + public long getElapsedRealtime() { + return elapsedRealtime; + } + + /** @hide */ + public void setElapsedRealtime(long time) { + elapsedRealtime = time; + } + + /** + * Return age of this {@link NetworkStats} object with respect to + * {@link SystemClock#elapsedRealtime()}. + * @hide + */ + public long getElapsedRealtimeAge() { + return SystemClock.elapsedRealtime() - elapsedRealtime; + } + + /** @hide */ + @UnsupportedAppUsage + public int size() { + return size; + } + + /** @hide */ + @VisibleForTesting + public int internalSize() { + return capacity; + } + + /** @hide */ + @Deprecated + public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + return combineValues( + iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, + txPackets, operations); + } + + /** @hide */ + public NetworkStats combineValues(String iface, int uid, int set, int tag, + long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + return combineValues(new Entry( + iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); + } + + /** + * Combine given values with an existing row, or create a new row if + * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can + * also be used to subtract values from existing rows. This method mutates the referencing + * {@link NetworkStats} object. + * + * @param entry the {@link Entry} to combine. + * @return a reference to this mutated {@link NetworkStats} object. + * @hide + */ + public @NonNull NetworkStats combineValues(@NonNull Entry entry) { + final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, + entry.roaming, entry.defaultNetwork); + if (i == -1) { + // only create new entry when positive contribution + insertEntry(entry); + } else { + rxBytes[i] += entry.rxBytes; + rxPackets[i] += entry.rxPackets; + txBytes[i] += entry.txBytes; + txPackets[i] += entry.txPackets; + operations[i] += entry.operations; + } + return this; + } + + /** + * Add given values with an existing row, or create a new row if + * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can + * also be used to subtract values from existing rows. + * + * @param entry the {@link Entry} to add. + * @return a new constructed {@link NetworkStats} object that contains the result. + */ + public @NonNull NetworkStats addEntry(@NonNull Entry entry) { + return this.clone().combineValues(entry); + } + + /** + * Add the given {@link NetworkStats} objects. + * + * @return the sum of two objects. + */ + public @NonNull NetworkStats add(@NonNull NetworkStats another) { + final NetworkStats ret = this.clone(); + ret.combineAllValues(another); + return ret; + } + + /** + * Combine all values from another {@link NetworkStats} into this object. + * @hide + */ + public void combineAllValues(@NonNull NetworkStats another) { + NetworkStats.Entry entry = null; + for (int i = 0; i < another.size; i++) { + entry = another.getValues(i, entry); + combineValues(entry); + } + } + + /** + * Find first stats index that matches the requested parameters. + * @hide + */ + public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, + int defaultNetwork) { + for (int i = 0; i < size; i++) { + if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] + && metered == this.metered[i] && roaming == this.roaming[i] + && defaultNetwork == this.defaultNetwork[i] + && Objects.equals(iface, this.iface[i])) { + return i; + } + } + return -1; + } + + /** + * Find first stats index that matches the requested parameters, starting + * search around the hinted index as an optimization. + * @hide + */ + @VisibleForTesting + public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, + int defaultNetwork, int hintIndex) { + for (int offset = 0; offset < size; offset++) { + final int halfOffset = offset / 2; + + // search outwards from hint index, alternating forward and backward + final int i; + if (offset % 2 == 0) { + i = (hintIndex + halfOffset) % size; + } else { + i = (size + hintIndex - halfOffset - 1) % size; + } + + if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] + && metered == this.metered[i] && roaming == this.roaming[i] + && defaultNetwork == this.defaultNetwork[i] + && Objects.equals(iface, this.iface[i])) { + return i; + } + } + return -1; + } + + /** + * Splice in {@link #operations} from the given {@link NetworkStats} based + * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, + * since operation counts are at data layer. + * @hide + */ + public void spliceOperationsFrom(NetworkStats stats) { + for (int i = 0; i < size; i++) { + final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i], + defaultNetwork[i]); + if (j == -1) { + operations[i] = 0; + } else { + operations[i] = stats.operations[j]; + } + } + } + + /** + * Return list of unique interfaces known by this data structure. + * @hide + */ + public String[] getUniqueIfaces() { + final HashSet<String> ifaces = new HashSet<String>(); + for (String iface : this.iface) { + if (iface != IFACE_ALL) { + ifaces.add(iface); + } + } + return ifaces.toArray(new String[ifaces.size()]); + } + + /** + * Return list of unique UIDs known by this data structure. + * @hide + */ + @UnsupportedAppUsage + public int[] getUniqueUids() { + final SparseBooleanArray uids = new SparseBooleanArray(); + for (int uid : this.uid) { + uids.put(uid, true); + } + + final int size = uids.size(); + final int[] result = new int[size]; + for (int i = 0; i < size; i++) { + result[i] = uids.keyAt(i); + } + return result; + } + + /** + * Return total bytes represented by this snapshot object, usually used when + * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. + * @hide + */ + @UnsupportedAppUsage + public long getTotalBytes() { + final Entry entry = getTotal(null); + return entry.rxBytes + entry.txBytes; + } + + /** + * Return total of all fields represented by this snapshot object. + * @hide + */ + @UnsupportedAppUsage + public Entry getTotal(Entry recycle) { + return getTotal(recycle, null, UID_ALL, false); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #uid}. + * @hide + */ + @UnsupportedAppUsage + public Entry getTotal(Entry recycle, int limitUid) { + return getTotal(recycle, null, limitUid, false); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #iface}. + * @hide + */ + public Entry getTotal(Entry recycle, HashSet<String> limitIface) { + return getTotal(recycle, limitIface, UID_ALL, false); + } + + /** @hide */ + @UnsupportedAppUsage + public Entry getTotalIncludingTags(Entry recycle) { + return getTotal(recycle, null, UID_ALL, true); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #iface} and {@link #uid}. + * + * @param limitIface Set of {@link #iface} to include in total; or {@code + * null} to include all ifaces. + */ + private Entry getTotal( + Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { + final Entry entry = recycle != null ? recycle : new Entry(); + + entry.iface = IFACE_ALL; + entry.uid = limitUid; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.metered = METERED_ALL; + entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; + entry.rxBytes = 0; + entry.rxPackets = 0; + entry.txBytes = 0; + entry.txPackets = 0; + entry.operations = 0; + + for (int i = 0; i < size; i++) { + final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); + final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); + + if (matchesUid && matchesIface) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE && !includeTags) continue; + + entry.rxBytes += rxBytes[i]; + entry.rxPackets += rxPackets[i]; + entry.txBytes += txBytes[i]; + entry.txPackets += txPackets[i]; + entry.operations += operations[i]; + } + } + return entry; + } + + /** + * Fast path for battery stats. + * @hide + */ + public long getTotalPackets() { + long total = 0; + for (int i = size-1; i >= 0; i--) { + total += rxPackets[i] + txPackets[i]; + } + return total; + } + + /** + * Subtract the given {@link NetworkStats}, effectively leaving the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. This method does not mutate + * the referencing object. + * + * @return the delta between two objects. + */ + public @NonNull NetworkStats subtract(@NonNull NetworkStats right) { + return subtract(this, right, null, null); + } + + /** + * Subtract the two given {@link NetworkStats} objects, returning the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + * <p> + * If counters have rolled backwards, they are clamped to {@code 0} and + * reported to the given {@link NonMonotonicObserver}. + * @hide + */ + public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, + NonMonotonicObserver<C> observer, C cookie) { + return subtract(left, right, observer, cookie, null); + } + + /** + * Subtract the two given {@link NetworkStats} objects, returning the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + * <p> + * If counters have rolled backwards, they are clamped to {@code 0} and + * reported to the given {@link NonMonotonicObserver}. + * <p> + * If <var>recycle</var> is supplied, this NetworkStats object will be + * reused (and returned) as the result if it is large enough to contain + * the data. + * @hide + */ + public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, + NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { + long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; + if (deltaRealtime < 0) { + if (observer != null) { + observer.foundNonMonotonic(left, -1, right, -1, cookie); + } + deltaRealtime = 0; + } + + // result will have our rows, and elapsed time between snapshots + final Entry entry = new Entry(); + final NetworkStats result; + if (recycle != null && recycle.capacity >= left.size) { + result = recycle; + result.size = 0; + result.elapsedRealtime = deltaRealtime; + } else { + result = new NetworkStats(deltaRealtime, left.size); + } + for (int i = 0; i < left.size; i++) { + entry.iface = left.iface[i]; + entry.uid = left.uid[i]; + entry.set = left.set[i]; + entry.tag = left.tag[i]; + entry.metered = left.metered[i]; + entry.roaming = left.roaming[i]; + entry.defaultNetwork = left.defaultNetwork[i]; + entry.rxBytes = left.rxBytes[i]; + entry.rxPackets = left.rxPackets[i]; + entry.txBytes = left.txBytes[i]; + entry.txPackets = left.txPackets[i]; + entry.operations = left.operations[i]; + + // find remote row that matches, and subtract + final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, + entry.metered, entry.roaming, entry.defaultNetwork, i); + if (j != -1) { + // Found matching row, subtract remote value. + entry.rxBytes -= right.rxBytes[j]; + entry.rxPackets -= right.rxPackets[j]; + entry.txBytes -= right.txBytes[j]; + entry.txPackets -= right.txPackets[j]; + entry.operations -= right.operations[j]; + } + + if (entry.isNegative()) { + if (observer != null) { + observer.foundNonMonotonic(left, i, right, j, cookie); + } + entry.rxBytes = Math.max(entry.rxBytes, 0); + entry.rxPackets = Math.max(entry.rxPackets, 0); + entry.txBytes = Math.max(entry.txBytes, 0); + entry.txPackets = Math.max(entry.txPackets, 0); + entry.operations = Math.max(entry.operations, 0); + } + + result.insertEntry(entry); + } + + return result; + } + + /** + * Calculate and apply adjustments to captured statistics for 464xlat traffic. + * + * <p>This mutates stacked traffic stats, to account for IPv4/IPv6 header size difference. + * + * <p>UID stats, which are only accounted on the stacked interface, need to be increased + * by 20 bytes/packet to account for translation overhead. + * + * <p>The potential additional overhead of 8 bytes/packet for ip fragments is ignored. + * + * <p>Interface stats need to sum traffic on both stacked and base interface because: + * - eBPF offloaded packets appear only on the stacked interface + * - Non-offloaded ingress packets appear only on the stacked interface + * (due to iptables raw PREROUTING drop rules) + * - Non-offloaded egress packets appear only on the stacked interface + * (due to ignoring traffic from clat daemon by uid match) + * (and of course the 20 bytes/packet overhead needs to be applied to stacked interface stats) + * + * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only + * {@code ConcurrentHashMap} + * @param baseTraffic Traffic on the base interfaces. Will be mutated. + * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. + * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. + * @hide + */ + public static void apply464xlatAdjustments(NetworkStats baseTraffic, + NetworkStats stackedTraffic, Map<String, String> stackedIfaces) { + // For recycling + Entry entry = null; + for (int i = 0; i < stackedTraffic.size; i++) { + entry = stackedTraffic.getValues(i, entry); + if (entry == null) continue; + if (entry.iface == null) continue; + if (!entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) continue; + + // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet + // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after + // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes + // difference for all packets (http://b/12249687, http:/b/33681750). + // + // Note: this doesn't account for LRO/GRO/GSO/TSO (ie. >mtu) traffic correctly, nor + // does it correctly account for the 8 extra bytes in the IPv6 fragmentation header. + // + // While the ebpf code path does try to simulate proper post segmentation packet + // counts, we have nothing of the sort of xt_qtaguid stats. + entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA; + entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA; + stackedTraffic.setValues(i, entry); + } + } + + /** + * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. + * + * <p>This mutates the object this method is called on. Equivalent to calling + * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as + * base and stacked traffic. + * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. + * @hide + */ + public void apply464xlatAdjustments(Map<String, String> stackedIfaces) { + apply464xlatAdjustments(this, this, stackedIfaces); + } + + /** + * Return total statistics grouped by {@link #iface}; doesn't mutate the + * original structure. + * @hide + */ + public NetworkStats groupedByIface() { + final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); + + final Entry entry = new Entry(); + entry.uid = UID_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.metered = METERED_ALL; + entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; + entry.operations = 0L; + + for (int i = 0; i < size; i++) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE) continue; + + entry.iface = iface[i]; + entry.rxBytes = rxBytes[i]; + entry.rxPackets = rxPackets[i]; + entry.txBytes = txBytes[i]; + entry.txPackets = txPackets[i]; + stats.combineValues(entry); + } + + return stats; + } + + /** + * Return total statistics grouped by {@link #uid}; doesn't mutate the + * original structure. + * @hide + */ + public NetworkStats groupedByUid() { + final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); + + final Entry entry = new Entry(); + entry.iface = IFACE_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.metered = METERED_ALL; + entry.roaming = ROAMING_ALL; + entry.defaultNetwork = DEFAULT_NETWORK_ALL; + + for (int i = 0; i < size; i++) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE) continue; + + entry.uid = uid[i]; + entry.rxBytes = rxBytes[i]; + entry.rxPackets = rxPackets[i]; + entry.txBytes = txBytes[i]; + entry.txPackets = txPackets[i]; + entry.operations = operations[i]; + stats.combineValues(entry); + } + + return stats; + } + + /** + * Remove all rows that match one of specified UIDs. + * This mutates the original structure in place. + * @hide + */ + public void removeUids(int[] uids) { + filter(e -> !ArrayUtils.contains(uids, e.uid)); + } + + /** + * Remove all rows that match one of specified UIDs. + * @return the result object. + * @hide + */ + @NonNull + public NetworkStats removeEmptyEntries() { + final NetworkStats ret = this.clone(); + ret.filter(e -> e.rxBytes != 0 || e.rxPackets != 0 || e.txBytes != 0 || e.txPackets != 0 + || e.operations != 0); + return ret; + } + + /** + * Only keep entries that match all specified filters. + * + * <p>This mutates the original structure in place. After this method is called, + * size is the number of matching entries, and capacity is the previous capacity. + * @param limitUid UID to filter for, or {@link #UID_ALL}. + * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}. + * @param limitTag Tag to filter for, or {@link #TAG_ALL}. + * @hide + */ + public void filter(int limitUid, String[] limitIfaces, int limitTag) { + if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { + return; + } + filter(e -> (limitUid == UID_ALL || limitUid == e.uid) + && (limitTag == TAG_ALL || limitTag == e.tag) + && (limitIfaces == INTERFACES_ALL + || ArrayUtils.contains(limitIfaces, e.iface))); + } + + /** + * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}. + * + * <p>This mutates the original structure in place. + * @hide + */ + public void filterDebugEntries() { + filter(e -> e.set < SET_DEBUG_START); + } + + private void filter(Predicate<Entry> predicate) { + Entry entry = new Entry(); + int nextOutputEntry = 0; + for (int i = 0; i < size; i++) { + entry = getValues(i, entry); + if (predicate.test(entry)) { + if (nextOutputEntry != i) { + setValues(nextOutputEntry, entry); + } + nextOutputEntry++; + } + } + size = nextOutputEntry; + } + + /** @hide */ + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); + for (int i = 0; i < size; i++) { + pw.print(prefix); + pw.print(" ["); pw.print(i); pw.print("]"); + pw.print(" iface="); pw.print(iface[i]); + pw.print(" uid="); pw.print(uid[i]); + pw.print(" set="); pw.print(setToString(set[i])); + pw.print(" tag="); pw.print(tagToString(tag[i])); + pw.print(" metered="); pw.print(meteredToString(metered[i])); + pw.print(" roaming="); pw.print(roamingToString(roaming[i])); + pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i])); + pw.print(" rxBytes="); pw.print(rxBytes[i]); + pw.print(" rxPackets="); pw.print(rxPackets[i]); + pw.print(" txBytes="); pw.print(txBytes[i]); + pw.print(" txPackets="); pw.print(txPackets[i]); + pw.print(" operations="); pw.println(operations[i]); + } + } + + /** + * Return text description of {@link #set} value. + * @hide + */ + public static String setToString(int set) { + switch (set) { + case SET_ALL: + return "ALL"; + case SET_DEFAULT: + return "DEFAULT"; + case SET_FOREGROUND: + return "FOREGROUND"; + case SET_DBG_VPN_IN: + return "DBG_VPN_IN"; + case SET_DBG_VPN_OUT: + return "DBG_VPN_OUT"; + default: + return "UNKNOWN"; + } + } + + /** + * Return text description of {@link #set} value. + * @hide + */ + public static String setToCheckinString(int set) { + switch (set) { + case SET_ALL: + return "all"; + case SET_DEFAULT: + return "def"; + case SET_FOREGROUND: + return "fg"; + case SET_DBG_VPN_IN: + return "vpnin"; + case SET_DBG_VPN_OUT: + return "vpnout"; + default: + return "unk"; + } + } + + /** + * @return true if the querySet matches the dataSet. + * @hide + */ + public static boolean setMatches(int querySet, int dataSet) { + if (querySet == dataSet) { + return true; + } + // SET_ALL matches all non-debugging sets. + return querySet == SET_ALL && dataSet < SET_DEBUG_START; + } + + /** + * Return text description of {@link #tag} value. + * @hide + */ + public static String tagToString(int tag) { + return "0x" + Integer.toHexString(tag); + } + + /** + * Return text description of {@link #metered} value. + * @hide + */ + public static String meteredToString(int metered) { + switch (metered) { + case METERED_ALL: + return "ALL"; + case METERED_NO: + return "NO"; + case METERED_YES: + return "YES"; + default: + return "UNKNOWN"; + } + } + + /** + * Return text description of {@link #roaming} value. + * @hide + */ + public static String roamingToString(int roaming) { + switch (roaming) { + case ROAMING_ALL: + return "ALL"; + case ROAMING_NO: + return "NO"; + case ROAMING_YES: + return "YES"; + default: + return "UNKNOWN"; + } + } + + /** + * Return text description of {@link #defaultNetwork} value. + * @hide + */ + public static String defaultNetworkToString(int defaultNetwork) { + switch (defaultNetwork) { + case DEFAULT_NETWORK_ALL: + return "ALL"; + case DEFAULT_NETWORK_NO: + return "NO"; + case DEFAULT_NETWORK_YES: + return "YES"; + default: + return "UNKNOWN"; + } + } + + /** @hide */ + @Override + public String toString() { + final CharArrayWriter writer = new CharArrayWriter(); + dump("", new PrintWriter(writer)); + return writer.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { + @Override + public NetworkStats createFromParcel(Parcel in) { + return new NetworkStats(in); + } + + @Override + public NetworkStats[] newArray(int size) { + return new NetworkStats[size]; + } + }; + + /** @hide */ + public interface NonMonotonicObserver<C> { + public void foundNonMonotonic( + NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); + public void foundNonMonotonic( + NetworkStats stats, int statsIndex, C cookie); + } + + /** + * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. + * + * <p>This method should only be called on delta NetworkStats. Do not call this method on a + * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change + * over time. + * + * <p>This method performs adjustments for one active VPN package and one VPN iface at a time. + * + * @param tunUid uid of the VPN application + * @param tunIface iface of the vpn tunnel + * @param underlyingIfaces underlying network ifaces used by the VPN application + * @hide + */ + public void migrateTun(int tunUid, @NonNull String tunIface, + @NonNull List<String> underlyingIfaces) { + // Combined usage by all apps using VPN. + final Entry tunIfaceTotal = new Entry(); + // Usage by VPN, grouped by its {@code underlyingIfaces}. + final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.size()]; + // Usage by VPN, summed across all its {@code underlyingIfaces}. + final Entry underlyingIfacesTotal = new Entry(); + + for (int i = 0; i < perInterfaceTotal.length; i++) { + perInterfaceTotal[i] = new Entry(); + } + + tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal, + underlyingIfacesTotal); + + // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app. + // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression. + // Negative stats should be avoided. + final Entry[] moved = + addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, + perInterfaceTotal, underlyingIfacesTotal); + deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved); + } + + /** + * Initializes the data used by the migrateTun() method. + * + * <p>This is the first pass iteration which does the following work: + * + * <ul> + * <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and + * background). + * <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself. + * </ul> + * + * @param tunUid uid of the VPN application + * @param tunIface iface of the vpn tunnel + * @param underlyingIfaces underlying network ifaces used by the VPN application + * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN + * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code + * underlyingIfaces} + * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its + * {@code underlyingIfaces} + */ + private void tunAdjustmentInit(int tunUid, @NonNull String tunIface, + @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, + @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { + final Entry recycle = new Entry(); + for (int i = 0; i < size; i++) { + getValues(i, recycle); + if (recycle.uid == UID_ALL) { + throw new IllegalStateException( + "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); + } + if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { + throw new IllegalStateException( + "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); + } + if (recycle.tag != TAG_NONE) { + // TODO(b/123666283): Take all tags for tunUid into account. + continue; + } + + if (tunUid == Process.SYSTEM_UID) { + // Kernel-based VPN or VCN, traffic sent by apps on the VPN/VCN network + // + // Since the data is not UID-accounted on underlying networks, just use VPN/VCN + // network usage as ground truth. Encrypted traffic on the underlying networks will + // never be processed here because encrypted traffic on the underlying interfaces + // is not present in UID stats, and this method is only called on UID stats. + if (tunIface.equals(recycle.iface)) { + tunIfaceTotal.add(recycle); + underlyingIfacesTotal.add(recycle); + + // In steady state, there should always be one network, but edge cases may + // result in the network being null (network lost), and thus no underlying + // ifaces is possible. + if (perInterfaceTotal.length > 0) { + // While platform VPNs and VCNs have exactly one underlying network, that + // network may have multiple interfaces (eg for 464xlat). This layer does + // not have the required information to identify which of the interfaces + // were used. Select "any" of the interfaces. Since overhead is already + // lost, this number is an approximation anyways. + perInterfaceTotal[0].add(recycle); + } + } + } else if (recycle.uid == tunUid) { + // VpnService VPN, traffic sent by the VPN app over underlying networks + for (int j = 0; j < underlyingIfaces.size(); j++) { + if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) { + perInterfaceTotal[j].add(recycle); + underlyingIfacesTotal.add(recycle); + break; + } + } + } else if (tunIface.equals(recycle.iface)) { + // VpnService VPN; traffic sent by apps on the VPN network + tunIfaceTotal.add(recycle); + } + } + } + + /** + * Distributes traffic across apps that are using given {@code tunIface}, and returns the total + * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}. + * + * @param tunUid uid of the VPN application + * @param tunIface iface of the vpn tunnel + * @param underlyingIfaces underlying network ifaces used by the VPN application + * @param tunIfaceTotal combined data usage across all apps using {@code tunIface} + * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces} + * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code + * underlyingIfaces} + */ + private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface, + @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, + @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { + // Traffic that should be moved off of each underlying interface for tunUid (see + // deductTrafficFromVpnApp below). + final Entry[] moved = new Entry[underlyingIfaces.size()]; + for (int i = 0; i < underlyingIfaces.size(); i++) { + moved[i] = new Entry(); + } + + final Entry tmpEntry = new Entry(); + final int origSize = size; + for (int i = 0; i < origSize; i++) { + if (!Objects.equals(iface[i], tunIface)) { + // Consider only entries that go onto the VPN interface. + continue; + } + + if (uid[i] == tunUid && tunUid != Process.SYSTEM_UID) { + // Exclude VPN app from the redistribution, as it can choose to create packet + // streams by writing to itself. + // + // However, for platform VPNs, do not exclude the system's usage of the VPN network, + // since it is never local-only, and never double counted + continue; + } + tmpEntry.uid = uid[i]; + tmpEntry.tag = tag[i]; + tmpEntry.metered = metered[i]; + tmpEntry.roaming = roaming[i]; + tmpEntry.defaultNetwork = defaultNetwork[i]; + + // In a first pass, compute this entry's total share of data across all + // underlyingIfaces. This is computed on the basis of the share of this entry's usage + // over tunIface. + // TODO: Consider refactoring first pass into a separate helper method. + long totalRxBytes = 0; + if (tunIfaceTotal.rxBytes > 0) { + // Note - The multiplication below should not overflow since NetworkStatsService + // processes this every time device has transmitted/received amount equivalent to + // global threshold alert (~ 2MB) across all interfaces. + final long rxBytesAcrossUnderlyingIfaces = + multiplySafeByRational(underlyingIfacesTotal.rxBytes, + rxBytes[i], tunIfaceTotal.rxBytes); + // app must not be blamed for more than it consumed on tunIface + totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces); + } + long totalRxPackets = 0; + if (tunIfaceTotal.rxPackets > 0) { + final long rxPacketsAcrossUnderlyingIfaces = + multiplySafeByRational(underlyingIfacesTotal.rxPackets, + rxPackets[i], tunIfaceTotal.rxPackets); + totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces); + } + long totalTxBytes = 0; + if (tunIfaceTotal.txBytes > 0) { + final long txBytesAcrossUnderlyingIfaces = + multiplySafeByRational(underlyingIfacesTotal.txBytes, + txBytes[i], tunIfaceTotal.txBytes); + totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces); + } + long totalTxPackets = 0; + if (tunIfaceTotal.txPackets > 0) { + final long txPacketsAcrossUnderlyingIfaces = + multiplySafeByRational(underlyingIfacesTotal.txPackets, + txPackets[i], tunIfaceTotal.txPackets); + totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces); + } + long totalOperations = 0; + if (tunIfaceTotal.operations > 0) { + final long operationsAcrossUnderlyingIfaces = + multiplySafeByRational(underlyingIfacesTotal.operations, + operations[i], tunIfaceTotal.operations); + totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces); + } + // In a second pass, distribute these values across interfaces in the proportion that + // each interface represents of the total traffic of the underlying interfaces. + for (int j = 0; j < underlyingIfaces.size(); j++) { + tmpEntry.iface = underlyingIfaces.get(j); + tmpEntry.rxBytes = 0; + // Reset 'set' to correct value since it gets updated when adding debug info below. + tmpEntry.set = set[i]; + if (underlyingIfacesTotal.rxBytes > 0) { + tmpEntry.rxBytes = + multiplySafeByRational(totalRxBytes, + perInterfaceTotal[j].rxBytes, + underlyingIfacesTotal.rxBytes); + } + tmpEntry.rxPackets = 0; + if (underlyingIfacesTotal.rxPackets > 0) { + tmpEntry.rxPackets = + multiplySafeByRational(totalRxPackets, + perInterfaceTotal[j].rxPackets, + underlyingIfacesTotal.rxPackets); + } + tmpEntry.txBytes = 0; + if (underlyingIfacesTotal.txBytes > 0) { + tmpEntry.txBytes = + multiplySafeByRational(totalTxBytes, + perInterfaceTotal[j].txBytes, + underlyingIfacesTotal.txBytes); + } + tmpEntry.txPackets = 0; + if (underlyingIfacesTotal.txPackets > 0) { + tmpEntry.txPackets = + multiplySafeByRational(totalTxPackets, + perInterfaceTotal[j].txPackets, + underlyingIfacesTotal.txPackets); + } + tmpEntry.operations = 0; + if (underlyingIfacesTotal.operations > 0) { + tmpEntry.operations = + multiplySafeByRational(totalOperations, + perInterfaceTotal[j].operations, + underlyingIfacesTotal.operations); + } + // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying + // interface. Add that data usage to this object. + combineValues(tmpEntry); + if (tag[i] == TAG_NONE) { + // Add the migrated data to moved so it is deducted from the VPN app later. + moved[j].add(tmpEntry); + // Add debug info + tmpEntry.set = SET_DBG_VPN_IN; + combineValues(tmpEntry); + } + } + } + return moved; + } + + private void deductTrafficFromVpnApp( + int tunUid, + @NonNull List<String> underlyingIfaces, + @NonNull Entry[] moved) { + if (tunUid == Process.SYSTEM_UID) { + // No traffic recorded on a per-UID basis for in-kernel VPN/VCNs over underlying + // networks; thus no traffic to deduct. + return; + } + + for (int i = 0; i < underlyingIfaces.size(); i++) { + moved[i].uid = tunUid; + // Add debug info + moved[i].set = SET_DBG_VPN_OUT; + moved[i].tag = TAG_NONE; + moved[i].iface = underlyingIfaces.get(i); + moved[i].metered = METERED_ALL; + moved[i].roaming = ROAMING_ALL; + moved[i].defaultNetwork = DEFAULT_NETWORK_ALL; + combineValues(moved[i]); + + // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than + // the TAG_NONE traffic. + // + // Relies on the fact that the underlying traffic only has state ROAMING_NO and + // METERED_NO, which should be the case as it comes directly from the /proc file. + // We only blend in the roaming data after applying these adjustments, by checking the + // NetworkIdentity of the underlying iface. + final int idxVpnBackground = findIndex(underlyingIfaces.get(i), tunUid, SET_DEFAULT, + TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); + if (idxVpnBackground != -1) { + // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed + // from foreground usage. + tunSubtract(idxVpnBackground, this, moved[i]); + } + + final int idxVpnForeground = findIndex(underlyingIfaces.get(i), tunUid, SET_FOREGROUND, + TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); + if (idxVpnForeground != -1) { + tunSubtract(idxVpnForeground, this, moved[i]); + } + } + } + + private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) { + long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); + left.rxBytes[i] -= rxBytes; + right.rxBytes -= rxBytes; + + long rxPackets = Math.min(left.rxPackets[i], right.rxPackets); + left.rxPackets[i] -= rxPackets; + right.rxPackets -= rxPackets; + + long txBytes = Math.min(left.txBytes[i], right.txBytes); + left.txBytes[i] -= txBytes; + right.txBytes -= txBytes; + + long txPackets = Math.min(left.txPackets[i], right.txPackets); + left.txPackets[i] -= txPackets; + right.txPackets -= txPackets; + } +} diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java new file mode 100644 index 0000000000..3885a9e6d5 --- /dev/null +++ b/framework-t/src/android/net/NetworkStatsAccess.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 android.net; + +import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; +import static android.net.NetworkStats.UID_ALL; +import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; + +import android.Manifest; +import android.annotation.IntDef; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; +import android.os.UserHandle; +import android.telephony.TelephonyManager; + +import com.android.server.LocalServices; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Utility methods for controlling access to network stats APIs. + * + * @hide + */ +public final class NetworkStatsAccess { + private NetworkStatsAccess() {} + + /** + * Represents an access level for the network usage history and statistics APIs. + * + * <p>Access levels are in increasing order; that is, it is reasonable to check access by + * verifying that the caller's access level is at least the minimum required level. + */ + @IntDef({ + Level.DEFAULT, + Level.USER, + Level.DEVICESUMMARY, + Level.DEVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Level { + /** + * Default, unprivileged access level. + * + * <p>Can only access usage for one's own UID. + * + * <p>Every app will have at least this access level. + */ + int DEFAULT = 0; + + /** + * Access level for apps which can access usage for any app running in the same user. + * + * <p>Granted to: + * <ul> + * <li>Profile owners. + * </ul> + */ + int USER = 1; + + /** + * Access level for apps which can access usage summary of device. Device summary includes + * usage by apps running in any profiles/users, however this access level does not + * allow querying usage of individual apps running in other profiles/users. + * + * <p>Granted to: + * <ul> + * <li>Apps with the PACKAGE_USAGE_STATS permission granted. Note that this is an AppOps bit + * so it is not necessarily sufficient to declare this in the manifest. + * <li>Apps with the (signature/privileged) READ_NETWORK_USAGE_HISTORY permission. + * </ul> + */ + int DEVICESUMMARY = 2; + + /** + * Access level for apps which can access usage for any app on the device, including apps + * running on other users/profiles. + * + * <p>Granted to: + * <ul> + * <li>Device owners. + * <li>Carrier-privileged applications. + * <li>The system UID. + * </ul> + */ + int DEVICE = 3; + } + + /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */ + public static @NetworkStatsAccess.Level int checkAccessLevel( + Context context, int callingUid, String callingPackage) { + final DevicePolicyManagerInternal dpmi = LocalServices.getService( + DevicePolicyManagerInternal.class); + final TelephonyManager tm = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + boolean hasCarrierPrivileges; + final long token = Binder.clearCallingIdentity(); + try { + hasCarrierPrivileges = tm != null + && tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } finally { + Binder.restoreCallingIdentity(token); + } + + final boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid); + final int appId = UserHandle.getAppId(callingUid); + if (hasCarrierPrivileges || isDeviceOwner + || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) { + // Carrier-privileged apps and device owners, and the system (including the + // network stack) can access data usage for all apps on the device. + return NetworkStatsAccess.Level.DEVICE; + } + + boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage); + if (hasAppOpsPermission || context.checkCallingOrSelfPermission( + READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) { + return NetworkStatsAccess.Level.DEVICESUMMARY; + } + + //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. + boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid) + || dpmi.isActiveDeviceOwner(callingUid)); + if (isProfileOwner) { + // Apps with the AppOps permission, profile owners, and apps with the privileged + // permission can access data usage for all apps in this user/profile. + return NetworkStatsAccess.Level.USER; + } + + // Everyone else gets default access (only to their own UID). + return NetworkStatsAccess.Level.DEFAULT; + } + + /** + * Returns whether the given caller should be able to access the given UID when the caller has + * the given {@link NetworkStatsAccess.Level}. + */ + public static boolean isAccessibleToUser(int uid, int callerUid, + @NetworkStatsAccess.Level int accessLevel) { + switch (accessLevel) { + case NetworkStatsAccess.Level.DEVICE: + // Device-level access - can access usage for any uid. + return true; + case NetworkStatsAccess.Level.DEVICESUMMARY: + // Can access usage for any app running in the same user, along + // with some special uids (system, removed, or tethering) and + // anonymized uids + return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED + || uid == UID_TETHERING || uid == UID_ALL + || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + case NetworkStatsAccess.Level.USER: + // User-level access - can access usage for any app running in the same user, along + // with some special uids (system, removed, or tethering). + return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED + || uid == UID_TETHERING + || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + case NetworkStatsAccess.Level.DEFAULT: + default: + // Default access level - can only access one's own usage. + return uid == callerUid; + } + } + + private static boolean hasAppOpsPermission( + Context context, int callingUid, String callingPackage) { + if (callingPackage != null) { + AppOpsManager appOps = (AppOpsManager) context.getSystemService( + Context.APP_OPS_SERVICE); + + final int mode = appOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, + callingUid, callingPackage); + if (mode == AppOpsManager.MODE_DEFAULT) { + // The default behavior here is to check if PackageManager has given the app + // permission. + final int permissionCheck = context.checkCallingPermission( + Manifest.permission.PACKAGE_USAGE_STATS); + return permissionCheck == PackageManager.PERMISSION_GRANTED; + } + return (mode == AppOpsManager.MODE_ALLOWED); + } + return false; + } +} diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java new file mode 100644 index 0000000000..0d3b9ed4e3 --- /dev/null +++ b/framework-t/src/android/net/NetworkStatsCollection.java @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.net; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.TrafficStats.UID_REMOVED; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; + +import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational; + +import android.os.Binder; +import android.service.NetworkStatsCollectionKeyProto; +import android.service.NetworkStatsCollectionProto; +import android.service.NetworkStatsCollectionStatsProto; +import android.telephony.SubscriptionPlan; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.IntArray; +import android.util.MathUtils; +import android.util.Range; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastDataInput; +import com.android.internal.util.FastDataOutput; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; + +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import libcore.io.IoUtils; + +import java.io.BufferedInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.ProtocolException; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Objects; + +/** + * Collection of {@link NetworkStatsHistory}, stored based on combined key of + * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. + * + * @hide + */ +public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer { + private static final String TAG = NetworkStatsCollection.class.getSimpleName(); + /** File header magic number: "ANET" */ + private static final int FILE_MAGIC = 0x414E4554; + + /** Default buffer size from BufferedInputStream */ + private static final int BUFFER_SIZE = 8192; + + private static final int VERSION_NETWORK_INIT = 1; + + private static final int VERSION_UID_INIT = 1; + private static final int VERSION_UID_WITH_IDENT = 2; + private static final int VERSION_UID_WITH_TAG = 3; + private static final int VERSION_UID_WITH_SET = 4; + + private static final int VERSION_UNIFIED_INIT = 16; + + private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>(); + + private final long mBucketDuration; + + private long mStartMillis; + private long mEndMillis; + private long mTotalBytes; + private boolean mDirty; + + public NetworkStatsCollection(long bucketDuration) { + mBucketDuration = bucketDuration; + reset(); + } + + public void clear() { + reset(); + } + + public void reset() { + mStats.clear(); + mStartMillis = Long.MAX_VALUE; + mEndMillis = Long.MIN_VALUE; + mTotalBytes = 0; + mDirty = false; + } + + public long getStartMillis() { + return mStartMillis; + } + + /** + * Return first atomic bucket in this collection, which is more conservative + * than {@link #mStartMillis}. + */ + public long getFirstAtomicBucketMillis() { + if (mStartMillis == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } else { + return mStartMillis + mBucketDuration; + } + } + + public long getEndMillis() { + return mEndMillis; + } + + public long getTotalBytes() { + return mTotalBytes; + } + + public boolean isDirty() { + return mDirty; + } + + public void clearDirty() { + mDirty = false; + } + + public boolean isEmpty() { + return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; + } + + @VisibleForTesting + public long roundUp(long time) { + if (time == Long.MIN_VALUE || time == Long.MAX_VALUE + || time == SubscriptionPlan.TIME_UNKNOWN) { + return time; + } else { + final long mod = time % mBucketDuration; + if (mod > 0) { + time -= mod; + time += mBucketDuration; + } + return time; + } + } + + @VisibleForTesting + public long roundDown(long time) { + if (time == Long.MIN_VALUE || time == Long.MAX_VALUE + || time == SubscriptionPlan.TIME_UNKNOWN) { + return time; + } else { + final long mod = time % mBucketDuration; + if (mod > 0) { + time -= mod; + } + return time; + } + } + + public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { + return getRelevantUids(accessLevel, Binder.getCallingUid()); + } + + public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, + final int callerUid) { + IntArray uids = new IntArray(); + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) { + int j = uids.binarySearch(key.uid); + + if (j < 0) { + j = ~j; + uids.add(j, key.uid); + } + } + } + return uids.toArray(); + } + + /** + * Combine all {@link NetworkStatsHistory} in this collection which match + * the requested parameters. + */ + public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, + int uid, int set, int tag, int fields, long start, long end, + @NetworkStatsAccess.Level int accessLevel, int callerUid) { + if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) { + throw new SecurityException("Network stats history of uid " + uid + + " is forbidden for caller " + callerUid); + } + + // 180 days of history should be enough for anyone; if we end up needing + // more, we'll dynamically grow the history object. + final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0, + (180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration); + final NetworkStatsHistory combined = new NetworkStatsHistory( + mBucketDuration, bucketEstimate, fields); + + // shortcut when we know stats will be empty + if (start == end) return combined; + + // Figure out the window of time that we should be augmenting (if any) + long augmentStart = SubscriptionPlan.TIME_UNKNOWN; + long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime() + : SubscriptionPlan.TIME_UNKNOWN; + // And if augmenting, we might need to collect more data to adjust with + long collectStart = start; + long collectEnd = end; + + if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) { + final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator(); + while (it.hasNext()) { + final Range<ZonedDateTime> cycle = it.next(); + final long cycleStart = cycle.getLower().toInstant().toEpochMilli(); + final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli(); + if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) { + augmentStart = cycleStart; + collectStart = Long.min(collectStart, augmentStart); + collectEnd = Long.max(collectEnd, augmentEnd); + break; + } + } + } + + if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { + // Shrink augmentation window so we don't risk undercounting. + augmentStart = roundUp(augmentStart); + augmentEnd = roundDown(augmentEnd); + // Grow collection window so we get all the stats needed. + collectStart = roundDown(collectStart); + collectEnd = roundUp(collectEnd); + } + + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag + && templateMatches(template, key.ident)) { + final NetworkStatsHistory value = mStats.valueAt(i); + combined.recordHistory(value, collectStart, collectEnd); + } + } + + if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { + final NetworkStatsHistory.Entry entry = combined.getValues( + augmentStart, augmentEnd, null); + + // If we don't have any recorded data for this time period, give + // ourselves something to scale with. + if (entry.rxBytes == 0 || entry.txBytes == 0) { + combined.recordData(augmentStart, augmentEnd, + new NetworkStats.Entry(1, 0, 1, 0, 0)); + combined.getValues(augmentStart, augmentEnd, entry); + } + + final long rawBytes = (entry.rxBytes + entry.txBytes) == 0 ? 1 : + (entry.rxBytes + entry.txBytes); + final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes; + final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes; + final long targetBytes = augmentPlan.getDataUsageBytes(); + + final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes); + final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes); + + + // Scale all matching buckets to reach anchor target + final long beforeTotal = combined.getTotalBytes(); + for (int i = 0; i < combined.size(); i++) { + combined.getValues(i, entry); + if (entry.bucketStart >= augmentStart + && entry.bucketStart + entry.bucketDuration <= augmentEnd) { + entry.rxBytes = multiplySafeByRational( + targetRxBytes, entry.rxBytes, rawRxBytes); + entry.txBytes = multiplySafeByRational( + targetTxBytes, entry.txBytes, rawTxBytes); + // We purposefully clear out packet counters to indicate + // that this data has been augmented. + entry.rxPackets = 0; + entry.txPackets = 0; + combined.setValues(i, entry); + } + } + + final long deltaTotal = combined.getTotalBytes() - beforeTotal; + if (deltaTotal != 0) { + Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); + } + + // Finally we can slice data as originally requested + final NetworkStatsHistory sliced = new NetworkStatsHistory( + mBucketDuration, bucketEstimate, fields); + sliced.recordHistory(combined, start, end); + return sliced; + } else { + return combined; + } + } + + /** + * Summarize all {@link NetworkStatsHistory} in this collection which match + * the requested parameters across the requested range. + * + * @param template - a predicate for filtering netstats. + * @param start - start of the range, timestamp in milliseconds since the epoch. + * @param end - end of the range, timestamp in milliseconds since the epoch. + * @param accessLevel - caller access level. + * @param callerUid - caller UID. + */ + public NetworkStats getSummary(NetworkTemplate template, long start, long end, + @NetworkStatsAccess.Level int accessLevel, int callerUid) { + final long now = System.currentTimeMillis(); + + final NetworkStats stats = new NetworkStats(end - start, 24); + + // shortcut when we know stats will be empty + if (start == end) return stats; + + final NetworkStats.Entry entry = new NetworkStats.Entry(); + NetworkStatsHistory.Entry historyEntry = null; + + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + if (templateMatches(template, key.ident) + && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel) + && key.set < NetworkStats.SET_DEBUG_START) { + final NetworkStatsHistory value = mStats.valueAt(i); + historyEntry = value.getValues(start, end, now, historyEntry); + + entry.iface = IFACE_ALL; + entry.uid = key.uid; + entry.set = key.set; + entry.tag = key.tag; + entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() + ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO; + entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO; + entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO; + entry.rxBytes = historyEntry.rxBytes; + entry.rxPackets = historyEntry.rxPackets; + entry.txBytes = historyEntry.txBytes; + entry.txPackets = historyEntry.txPackets; + entry.operations = historyEntry.operations; + + if (!entry.isEmpty()) { + stats.combineValues(entry); + } + } + } + + return stats; + } + + /** + * Record given {@link android.net.NetworkStats.Entry} into this collection. + */ + public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, + long end, NetworkStats.Entry entry) { + final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag); + history.recordData(start, end, entry); + noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes); + } + + /** + * Record given {@link NetworkStatsHistory} into this collection. + */ + private void recordHistory(Key key, NetworkStatsHistory history) { + if (history.size() == 0) return; + noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); + + NetworkStatsHistory target = mStats.get(key); + if (target == null) { + target = new NetworkStatsHistory(history.getBucketDuration()); + mStats.put(key, target); + } + target.recordEntireHistory(history); + } + + /** + * Record all {@link NetworkStatsHistory} contained in the given collection + * into this collection. + */ + public void recordCollection(NetworkStatsCollection another) { + for (int i = 0; i < another.mStats.size(); i++) { + final Key key = another.mStats.keyAt(i); + final NetworkStatsHistory value = another.mStats.valueAt(i); + recordHistory(key, value); + } + } + + private NetworkStatsHistory findOrCreateHistory( + NetworkIdentitySet ident, int uid, int set, int tag) { + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory existing = mStats.get(key); + + // update when no existing, or when bucket duration changed + NetworkStatsHistory updated = null; + if (existing == null) { + updated = new NetworkStatsHistory(mBucketDuration, 10); + } else if (existing.getBucketDuration() != mBucketDuration) { + updated = new NetworkStatsHistory(existing, mBucketDuration); + } + + if (updated != null) { + mStats.put(key, updated); + return updated; + } else { + return existing; + } + } + + @Override + public void read(InputStream in) throws IOException { + final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE); + read(dataIn); + } + + private void read(DataInput in) throws IOException { + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UNIFIED_INIT: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = in.readInt(); + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + recordHistory(key, history); + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } + + @Override + public void write(OutputStream out) throws IOException { + final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE); + write(dataOut); + dataOut.flush(); + } + + private void write(DataOutput out) throws IOException { + // cluster key lists grouped by ident + final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); + for (Key key : mStats.keySet()) { + ArrayList<Key> keys = keysByIdent.get(key.ident); + if (keys == null) { + keys = Lists.newArrayList(); + keysByIdent.put(key.ident, keys); + } + keys.add(key); + } + + out.writeInt(FILE_MAGIC); + out.writeInt(VERSION_UNIFIED_INIT); + + out.writeInt(keysByIdent.size()); + for (NetworkIdentitySet ident : keysByIdent.keySet()) { + final ArrayList<Key> keys = keysByIdent.get(ident); + ident.writeToStream(out); + + out.writeInt(keys.size()); + for (Key key : keys) { + final NetworkStatsHistory history = mStats.get(key); + out.writeInt(key.uid); + out.writeInt(key.set); + out.writeInt(key.tag); + history.writeToStream(out); + } + } + } + + /** + * Read legacy network summary statistics file format into the collection, + * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. + * + * @deprecated + */ + @Deprecated + public void readLegacyNetwork(File file) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_NETWORK_INIT: { + // network := size *(NetworkIdentitySet NetworkStatsHistory) + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); + recordHistory(key, history); + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + /** + * Read legacy Uid statistics file format into the collection, + * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. + * + * @deprecated + */ + @Deprecated + public void readLegacyUid(File file, boolean onlyTags) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UID_INIT: { + // uid := size *(UID NetworkStatsHistory) + + // drop this data version, since we don't have a good + // mapping into NetworkIdentitySet. + break; + } + case VERSION_UID_WITH_IDENT: { + // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) + + // drop this data version, since this version only existed + // for a short time. + break; + } + case VERSION_UID_WITH_TAG: + case VERSION_UID_WITH_SET: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() + : SET_DEFAULT; + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + if ((tag == TAG_NONE) != onlyTags) { + recordHistory(key, history); + } + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + /** + * Remove any {@link NetworkStatsHistory} attributed to the requested UID, + * moving any {@link NetworkStats#TAG_NONE} series to + * {@link TrafficStats#UID_REMOVED}. + */ + public void removeUids(int[] uids) { + final ArrayList<Key> knownKeys = Lists.newArrayList(); + knownKeys.addAll(mStats.keySet()); + + // migrate all UID stats into special "removed" bucket + for (Key key : knownKeys) { + if (ArrayUtils.contains(uids, key.uid)) { + // only migrate combined TAG_NONE history + if (key.tag == TAG_NONE) { + final NetworkStatsHistory uidHistory = mStats.get(key); + final NetworkStatsHistory removedHistory = findOrCreateHistory( + key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); + removedHistory.recordEntireHistory(uidHistory); + } + mStats.remove(key); + mDirty = true; + } + } + } + + private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { + if (startMillis < mStartMillis) mStartMillis = startMillis; + if (endMillis > mEndMillis) mEndMillis = endMillis; + mTotalBytes += totalBytes; + mDirty = true; + } + + private int estimateBuckets() { + return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) + / mBucketDuration); + } + + private ArrayList<Key> getSortedKeys() { + final ArrayList<Key> keys = Lists.newArrayList(); + keys.addAll(mStats.keySet()); + Collections.sort(keys); + return keys; + } + + public void dump(IndentingPrintWriter pw) { + for (Key key : getSortedKeys()) { + pw.print("ident="); pw.print(key.ident.toString()); + pw.print(" uid="); pw.print(key.uid); + pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); + pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); + + final NetworkStatsHistory history = mStats.get(key); + pw.increaseIndent(); + history.dump(pw, true); + pw.decreaseIndent(); + } + } + + public void dumpDebug(ProtoOutputStream proto, long tag) { + final long start = proto.start(tag); + + for (Key key : getSortedKeys()) { + final long startStats = proto.start(NetworkStatsCollectionProto.STATS); + + // Key + final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY); + key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY); + proto.write(NetworkStatsCollectionKeyProto.UID, key.uid); + proto.write(NetworkStatsCollectionKeyProto.SET, key.set); + proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag); + proto.end(startKey); + + // Value + final NetworkStatsHistory history = mStats.get(key); + history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY); + proto.end(startStats); + } + + proto.end(start); + } + + public void dumpCheckin(PrintWriter pw, long start, long end) { + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); + } + + /** + * Dump all contained stats that match requested parameters, but group + * together all matching {@link NetworkTemplate} under a single prefix. + */ + private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, + String groupPrefix) { + final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>(); + + // Walk through all history, grouping by matching network templates + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + final NetworkStatsHistory value = mStats.valueAt(i); + + if (!templateMatches(groupTemplate, key.ident)) continue; + if (key.set >= NetworkStats.SET_DEBUG_START) continue; + + final Key groupKey = new Key(null, key.uid, key.set, key.tag); + NetworkStatsHistory groupHistory = grouped.get(groupKey); + if (groupHistory == null) { + groupHistory = new NetworkStatsHistory(value.getBucketDuration()); + grouped.put(groupKey, groupHistory); + } + groupHistory.recordHistory(value, start, end); + } + + for (int i = 0; i < grouped.size(); i++) { + final Key key = grouped.keyAt(i); + final NetworkStatsHistory value = grouped.valueAt(i); + + if (value.size() == 0) continue; + + pw.print("c,"); + pw.print(groupPrefix); pw.print(','); + pw.print(key.uid); pw.print(','); + pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); + pw.print(key.tag); + pw.println(); + + value.dumpCheckin(pw); + } + } + + /** + * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} + * in the given {@link NetworkIdentitySet}. + */ + private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { + for (NetworkIdentity ident : identSet) { + if (template.matches(ident)) { + return true; + } + } + return false; + } + + private static class Key implements Comparable<Key> { + public final NetworkIdentitySet ident; + public final int uid; + public final int set; + public final int tag; + + private final int mHashCode; + + Key(NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = ident; + this.uid = uid; + this.set = set; + this.tag = tag; + mHashCode = Objects.hash(ident, uid, set, tag); + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Key) { + final Key key = (Key) obj; + return uid == key.uid && set == key.set && tag == key.tag + && Objects.equals(ident, key.ident); + } + return false; + } + + @Override + public int compareTo(Key another) { + int res = 0; + if (ident != null && another.ident != null) { + res = ident.compareTo(another.ident); + } + if (res == 0) { + res = Integer.compare(uid, another.uid); + } + if (res == 0) { + res = Integer.compare(set, another.set); + } + if (res == 0) { + res = Integer.compare(tag, another.tag); + } + return res; + } + } +} diff --git a/framework-t/src/android/net/NetworkStatsHistory.aidl b/framework-t/src/android/net/NetworkStatsHistory.aidl new file mode 100644 index 0000000000..8b9069f8fa --- /dev/null +++ b/framework-t/src/android/net/NetworkStatsHistory.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, The Android Open Source Project + * + * 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 android.net; + +parcelable NetworkStatsHistory; diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java new file mode 100644 index 0000000000..a875e1ad45 --- /dev/null +++ b/framework-t/src/android/net/NetworkStatsHistory.java @@ -0,0 +1,881 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; +import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; +import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; +import static android.net.NetworkStatsHistory.Entry.UNKNOWN; +import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; +import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; + +import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational; +import static com.android.internal.util.ArrayUtils.total; + +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.service.NetworkStatsHistoryBucketProto; +import android.service.NetworkStatsHistoryProto; +import android.util.MathUtils; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.IndentingPrintWriter; + +import libcore.util.EmptyArray; + +import java.io.CharArrayWriter; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.ProtocolException; +import java.util.Arrays; +import java.util.Random; + +/** + * Collection of historical network statistics, recorded into equally-sized + * "buckets" in time. Internally it stores data in {@code long} series for more + * efficient persistence. + * <p> + * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for + * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is + * sorted at all times. + * + * @hide + */ +public class NetworkStatsHistory implements Parcelable { + private static final int VERSION_INIT = 1; + private static final int VERSION_ADD_PACKETS = 2; + private static final int VERSION_ADD_ACTIVE = 3; + + public static final int FIELD_ACTIVE_TIME = 0x01; + public static final int FIELD_RX_BYTES = 0x02; + public static final int FIELD_RX_PACKETS = 0x04; + public static final int FIELD_TX_BYTES = 0x08; + public static final int FIELD_TX_PACKETS = 0x10; + public static final int FIELD_OPERATIONS = 0x20; + + public static final int FIELD_ALL = 0xFFFFFFFF; + + private long bucketDuration; + private int bucketCount; + private long[] bucketStart; + private long[] activeTime; + private long[] rxBytes; + private long[] rxPackets; + private long[] txBytes; + private long[] txPackets; + private long[] operations; + private long totalBytes; + + public static class Entry { + public static final long UNKNOWN = -1; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long bucketDuration; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long bucketStart; + public long activeTime; + @UnsupportedAppUsage + public long rxBytes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long rxPackets; + @UnsupportedAppUsage + public long txBytes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public long txPackets; + public long operations; + } + + @UnsupportedAppUsage + public NetworkStatsHistory(long bucketDuration) { + this(bucketDuration, 10, FIELD_ALL); + } + + public NetworkStatsHistory(long bucketDuration, int initialSize) { + this(bucketDuration, initialSize, FIELD_ALL); + } + + public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { + this.bucketDuration = bucketDuration; + bucketStart = new long[initialSize]; + if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize]; + if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; + if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; + if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; + if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; + if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; + bucketCount = 0; + totalBytes = 0; + } + + public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { + this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); + recordEntireHistory(existing); + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkStatsHistory(Parcel in) { + bucketDuration = in.readLong(); + bucketStart = readLongArray(in); + activeTime = readLongArray(in); + rxBytes = readLongArray(in); + rxPackets = readLongArray(in); + txBytes = readLongArray(in); + txPackets = readLongArray(in); + operations = readLongArray(in); + bucketCount = bucketStart.length; + totalBytes = in.readLong(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(bucketDuration); + writeLongArray(out, bucketStart, bucketCount); + writeLongArray(out, activeTime, bucketCount); + writeLongArray(out, rxBytes, bucketCount); + writeLongArray(out, rxPackets, bucketCount); + writeLongArray(out, txBytes, bucketCount); + writeLongArray(out, txPackets, bucketCount); + writeLongArray(out, operations, bucketCount); + out.writeLong(totalBytes); + } + + public NetworkStatsHistory(DataInput in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: { + bucketDuration = in.readLong(); + bucketStart = readFullLongArray(in); + rxBytes = readFullLongArray(in); + rxPackets = new long[bucketStart.length]; + txBytes = readFullLongArray(in); + txPackets = new long[bucketStart.length]; + operations = new long[bucketStart.length]; + bucketCount = bucketStart.length; + totalBytes = total(rxBytes) + total(txBytes); + break; + } + case VERSION_ADD_PACKETS: + case VERSION_ADD_ACTIVE: { + bucketDuration = in.readLong(); + bucketStart = readVarLongArray(in); + activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in) + : new long[bucketStart.length]; + rxBytes = readVarLongArray(in); + rxPackets = readVarLongArray(in); + txBytes = readVarLongArray(in); + txPackets = readVarLongArray(in); + operations = readVarLongArray(in); + bucketCount = bucketStart.length; + totalBytes = total(rxBytes) + total(txBytes); + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + + if (bucketStart.length != bucketCount || rxBytes.length != bucketCount + || rxPackets.length != bucketCount || txBytes.length != bucketCount + || txPackets.length != bucketCount || operations.length != bucketCount) { + throw new ProtocolException("Mismatched history lengths"); + } + } + + public void writeToStream(DataOutput out) throws IOException { + out.writeInt(VERSION_ADD_ACTIVE); + out.writeLong(bucketDuration); + writeVarLongArray(out, bucketStart, bucketCount); + writeVarLongArray(out, activeTime, bucketCount); + writeVarLongArray(out, rxBytes, bucketCount); + writeVarLongArray(out, rxPackets, bucketCount); + writeVarLongArray(out, txBytes, bucketCount); + writeVarLongArray(out, txPackets, bucketCount); + writeVarLongArray(out, operations, bucketCount); + } + + @Override + public int describeContents() { + return 0; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public int size() { + return bucketCount; + } + + public long getBucketDuration() { + return bucketDuration; + } + + @UnsupportedAppUsage + public long getStart() { + if (bucketCount > 0) { + return bucketStart[0]; + } else { + return Long.MAX_VALUE; + } + } + + @UnsupportedAppUsage + public long getEnd() { + if (bucketCount > 0) { + return bucketStart[bucketCount - 1] + bucketDuration; + } else { + return Long.MIN_VALUE; + } + } + + /** + * Return total bytes represented by this history. + */ + public long getTotalBytes() { + return totalBytes; + } + + /** + * Return index of bucket that contains or is immediately before the + * requested time. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public int getIndexBefore(long time) { + int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); + if (index < 0) { + index = (~index) - 1; + } else { + index -= 1; + } + return MathUtils.constrain(index, 0, bucketCount - 1); + } + + /** + * Return index of bucket that contains or is immediately after the + * requested time. + */ + public int getIndexAfter(long time) { + int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); + if (index < 0) { + index = ~index; + } else { + index += 1; + } + return MathUtils.constrain(index, 0, bucketCount - 1); + } + + /** + * Return specific stats entry. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public Entry getValues(int i, Entry recycle) { + final Entry entry = recycle != null ? recycle : new Entry(); + entry.bucketStart = bucketStart[i]; + entry.bucketDuration = bucketDuration; + entry.activeTime = getLong(activeTime, i, UNKNOWN); + entry.rxBytes = getLong(rxBytes, i, UNKNOWN); + entry.rxPackets = getLong(rxPackets, i, UNKNOWN); + entry.txBytes = getLong(txBytes, i, UNKNOWN); + entry.txPackets = getLong(txPackets, i, UNKNOWN); + entry.operations = getLong(operations, i, UNKNOWN); + return entry; + } + + public void setValues(int i, Entry entry) { + // Unwind old values + if (rxBytes != null) totalBytes -= rxBytes[i]; + if (txBytes != null) totalBytes -= txBytes[i]; + + bucketStart[i] = entry.bucketStart; + setLong(activeTime, i, entry.activeTime); + setLong(rxBytes, i, entry.rxBytes); + setLong(rxPackets, i, entry.rxPackets); + setLong(txBytes, i, entry.txBytes); + setLong(txPackets, i, entry.txPackets); + setLong(operations, i, entry.operations); + + // Apply new values + if (rxBytes != null) totalBytes += rxBytes[i]; + if (txBytes != null) totalBytes += txBytes[i]; + } + + /** + * Record that data traffic occurred in the given time range. Will + * distribute across internal buckets, creating new buckets as needed. + */ + @Deprecated + public void recordData(long start, long end, long rxBytes, long txBytes) { + recordData(start, end, new NetworkStats.Entry( + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); + } + + /** + * Record that data traffic occurred in the given time range. Will + * distribute across internal buckets, creating new buckets as needed. + */ + public void recordData(long start, long end, NetworkStats.Entry entry) { + long rxBytes = entry.rxBytes; + long rxPackets = entry.rxPackets; + long txBytes = entry.txBytes; + long txPackets = entry.txPackets; + long operations = entry.operations; + + if (entry.isNegative()) { + throw new IllegalArgumentException("tried recording negative data"); + } + if (entry.isEmpty()) { + return; + } + + // create any buckets needed by this range + ensureBuckets(start, end); + + // distribute data usage into buckets + long duration = end - start; + final int startIndex = getIndexAfter(end); + for (int i = startIndex; i >= 0; i--) { + final long curStart = bucketStart[i]; + final long curEnd = curStart + bucketDuration; + + // bucket is older than record; we're finished + if (curEnd < start) break; + // bucket is newer than record; keep looking + if (curStart > end) continue; + + final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); + if (overlap <= 0) continue; + + // integer math each time is faster than floating point + final long fracRxBytes = multiplySafeByRational(rxBytes, overlap, duration); + final long fracRxPackets = multiplySafeByRational(rxPackets, overlap, duration); + final long fracTxBytes = multiplySafeByRational(txBytes, overlap, duration); + final long fracTxPackets = multiplySafeByRational(txPackets, overlap, duration); + final long fracOperations = multiplySafeByRational(operations, overlap, duration); + + + addLong(activeTime, i, overlap); + addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes; + addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets; + addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes; + addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets; + addLong(this.operations, i, fracOperations); operations -= fracOperations; + + duration -= overlap; + } + + totalBytes += entry.rxBytes + entry.txBytes; + } + + /** + * Record an entire {@link NetworkStatsHistory} into this history. Usually + * for combining together stats for external reporting. + */ + @UnsupportedAppUsage + public void recordEntireHistory(NetworkStatsHistory input) { + recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE); + } + + /** + * Record given {@link NetworkStatsHistory} into this history, copying only + * buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. + */ + public void recordHistory(NetworkStatsHistory input, long start, long end) { + final NetworkStats.Entry entry = new NetworkStats.Entry( + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + for (int i = 0; i < input.bucketCount; i++) { + final long bucketStart = input.bucketStart[i]; + final long bucketEnd = bucketStart + input.bucketDuration; + + // skip when bucket is outside requested range + if (bucketStart < start || bucketEnd > end) continue; + + entry.rxBytes = getLong(input.rxBytes, i, 0L); + entry.rxPackets = getLong(input.rxPackets, i, 0L); + entry.txBytes = getLong(input.txBytes, i, 0L); + entry.txPackets = getLong(input.txPackets, i, 0L); + entry.operations = getLong(input.operations, i, 0L); + + recordData(bucketStart, bucketEnd, entry); + } + } + + /** + * Ensure that buckets exist for given time range, creating as needed. + */ + private void ensureBuckets(long start, long end) { + // normalize incoming range to bucket boundaries + start -= start % bucketDuration; + end += (bucketDuration - (end % bucketDuration)) % bucketDuration; + + for (long now = start; now < end; now += bucketDuration) { + // try finding existing bucket + final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); + if (index < 0) { + // bucket missing, create and insert + insertBucket(~index, now); + } + } + } + + /** + * Insert new bucket at requested index and starting time. + */ + private void insertBucket(int index, long start) { + // create more buckets when needed + if (bucketCount >= bucketStart.length) { + final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; + bucketStart = Arrays.copyOf(bucketStart, newLength); + if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength); + if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); + if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); + if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); + if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); + if (operations != null) operations = Arrays.copyOf(operations, newLength); + } + + // create gap when inserting bucket in middle + if (index < bucketCount) { + final int dstPos = index + 1; + final int length = bucketCount - index; + + System.arraycopy(bucketStart, index, bucketStart, dstPos, length); + if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length); + if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); + if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); + if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); + if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); + if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); + } + + bucketStart[index] = start; + setLong(activeTime, index, 0L); + setLong(rxBytes, index, 0L); + setLong(rxPackets, index, 0L); + setLong(txBytes, index, 0L); + setLong(txPackets, index, 0L); + setLong(operations, index, 0L); + bucketCount++; + } + + /** + * Clear all data stored in this object. + */ + public void clear() { + bucketStart = EmptyArray.LONG; + if (activeTime != null) activeTime = EmptyArray.LONG; + if (rxBytes != null) rxBytes = EmptyArray.LONG; + if (rxPackets != null) rxPackets = EmptyArray.LONG; + if (txBytes != null) txBytes = EmptyArray.LONG; + if (txPackets != null) txPackets = EmptyArray.LONG; + if (operations != null) operations = EmptyArray.LONG; + bucketCount = 0; + totalBytes = 0; + } + + /** + * Remove buckets older than requested cutoff. + */ + @Deprecated + public void removeBucketsBefore(long cutoff) { + int i; + for (i = 0; i < bucketCount; i++) { + final long curStart = bucketStart[i]; + final long curEnd = curStart + bucketDuration; + + // cutoff happens before or during this bucket; everything before + // this bucket should be removed. + if (curEnd > cutoff) break; + } + + if (i > 0) { + final int length = bucketStart.length; + bucketStart = Arrays.copyOfRange(bucketStart, i, length); + if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length); + if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); + if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); + if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); + if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); + if (operations != null) operations = Arrays.copyOfRange(operations, i, length); + bucketCount -= i; + + // TODO: subtract removed values from totalBytes + } + } + + /** + * Return interpolated data usage across the requested range. Interpolates + * across buckets, so values may be rounded slightly. + * + * <p>If the active bucket is not completed yet, it returns the proportional value of it + * based on its duration and the {@code end} param. + * + * @param start - start of the range, timestamp in milliseconds since the epoch. + * @param end - end of the range, timestamp in milliseconds since the epoch. + * @param recycle - entry instance for performance, could be null. + */ + @UnsupportedAppUsage + public Entry getValues(long start, long end, Entry recycle) { + return getValues(start, end, Long.MAX_VALUE, recycle); + } + + /** + * Return interpolated data usage across the requested range. Interpolates + * across buckets, so values may be rounded slightly. + * + * @param start - start of the range, timestamp in milliseconds since the epoch. + * @param end - end of the range, timestamp in milliseconds since the epoch. + * @param now - current timestamp in milliseconds since the epoch (wall clock). + * @param recycle - entry instance for performance, could be null. + */ + @UnsupportedAppUsage + public Entry getValues(long start, long end, long now, Entry recycle) { + final Entry entry = recycle != null ? recycle : new Entry(); + entry.bucketDuration = end - start; + entry.bucketStart = start; + entry.activeTime = activeTime != null ? 0 : UNKNOWN; + entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; + entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; + entry.txBytes = txBytes != null ? 0 : UNKNOWN; + entry.txPackets = txPackets != null ? 0 : UNKNOWN; + entry.operations = operations != null ? 0 : UNKNOWN; + + final int startIndex = getIndexAfter(end); + for (int i = startIndex; i >= 0; i--) { + final long curStart = bucketStart[i]; + long curEnd = curStart + bucketDuration; + + // bucket is older than request; we're finished + if (curEnd <= start) break; + // bucket is newer than request; keep looking + if (curStart >= end) continue; + + // the active bucket is shorter then a normal completed bucket + if (curEnd > now) curEnd = now; + // usually this is simply bucketDuration + final long bucketSpan = curEnd - curStart; + // prevent division by zero + if (bucketSpan <= 0) continue; + + final long overlapEnd = curEnd < end ? curEnd : end; + final long overlapStart = curStart > start ? curStart : start; + final long overlap = overlapEnd - overlapStart; + if (overlap <= 0) continue; + + // integer math each time is faster than floating point + if (activeTime != null) { + entry.activeTime += multiplySafeByRational(activeTime[i], overlap, bucketSpan); + } + if (rxBytes != null) { + entry.rxBytes += multiplySafeByRational(rxBytes[i], overlap, bucketSpan); + } + if (rxPackets != null) { + entry.rxPackets += multiplySafeByRational(rxPackets[i], overlap, bucketSpan); + } + if (txBytes != null) { + entry.txBytes += multiplySafeByRational(txBytes[i], overlap, bucketSpan); + } + if (txPackets != null) { + entry.txPackets += multiplySafeByRational(txPackets[i], overlap, bucketSpan); + } + if (operations != null) { + entry.operations += multiplySafeByRational(operations[i], overlap, bucketSpan); + } + } + return entry; + } + + /** + * @deprecated only for temporary testing + */ + @Deprecated + public void generateRandom(long start, long end, long bytes) { + final Random r = new Random(); + + final float fractionRx = r.nextFloat(); + final long rxBytes = (long) (bytes * fractionRx); + final long txBytes = (long) (bytes * (1 - fractionRx)); + + final long rxPackets = rxBytes / 1024; + final long txPackets = txBytes / 1024; + final long operations = rxBytes / 2048; + + generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r); + } + + /** + * @deprecated only for temporary testing + */ + @Deprecated + public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, + long txPackets, long operations, Random r) { + ensureBuckets(start, end); + + final NetworkStats.Entry entry = new NetworkStats.Entry( + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 + || operations > 32) { + final long curStart = randomLong(r, start, end); + final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2); + + entry.rxBytes = randomLong(r, 0, rxBytes); + entry.rxPackets = randomLong(r, 0, rxPackets); + entry.txBytes = randomLong(r, 0, txBytes); + entry.txPackets = randomLong(r, 0, txPackets); + entry.operations = randomLong(r, 0, operations); + + rxBytes -= entry.rxBytes; + rxPackets -= entry.rxPackets; + txBytes -= entry.txBytes; + txPackets -= entry.txPackets; + operations -= entry.operations; + + recordData(curStart, curEnd, entry); + } + } + + public static long randomLong(Random r, long start, long end) { + return (long) (start + (r.nextFloat() * (end - start))); + } + + /** + * Quickly determine if this history intersects with given window. + */ + public boolean intersects(long start, long end) { + final long dataStart = getStart(); + final long dataEnd = getEnd(); + if (start >= dataStart && start <= dataEnd) return true; + if (end >= dataStart && end <= dataEnd) return true; + if (dataStart >= start && dataStart <= end) return true; + if (dataEnd >= start && dataEnd <= end) return true; + return false; + } + + public void dump(IndentingPrintWriter pw, boolean fullHistory) { + pw.print("NetworkStatsHistory: bucketDuration="); + pw.println(bucketDuration / SECOND_IN_MILLIS); + pw.increaseIndent(); + + final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); + if (start > 0) { + pw.print("(omitting "); pw.print(start); pw.println(" buckets)"); + } + + for (int i = start; i < bucketCount; i++) { + pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS); + if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); } + if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); } + if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); } + if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); } + if (operations != null) { pw.print(" op="); pw.print(operations[i]); } + pw.println(); + } + + pw.decreaseIndent(); + } + + public void dumpCheckin(PrintWriter pw) { + pw.print("d,"); + pw.print(bucketDuration / SECOND_IN_MILLIS); + pw.println(); + + for (int i = 0; i < bucketCount; i++) { + pw.print("b,"); + pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(','); + if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(','); + if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(','); + if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(','); + if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(','); + if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); } + pw.println(); + } + } + + public void dumpDebug(ProtoOutputStream proto, long tag) { + final long start = proto.start(tag); + + proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration); + + for (int i = 0; i < bucketCount; i++) { + final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS); + + proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS, bucketStart[i]); + dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i); + dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i); + dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i); + dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i); + dumpDebug(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i); + + proto.end(startBucket); + } + + proto.end(start); + } + + private static void dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index) { + if (array != null) { + proto.write(tag, array[index]); + } + } + + @Override + public String toString() { + final CharArrayWriter writer = new CharArrayWriter(); + dump(new IndentingPrintWriter(writer, " "), false); + return writer.toString(); + } + + @UnsupportedAppUsage + public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { + @Override + public NetworkStatsHistory createFromParcel(Parcel in) { + return new NetworkStatsHistory(in); + } + + @Override + public NetworkStatsHistory[] newArray(int size) { + return new NetworkStatsHistory[size]; + } + }; + + private static long getLong(long[] array, int i, long value) { + return array != null ? array[i] : value; + } + + private static void setLong(long[] array, int i, long value) { + if (array != null) array[i] = value; + } + + private static void addLong(long[] array, int i, long value) { + if (array != null) array[i] += value; + } + + public int estimateResizeBuckets(long newBucketDuration) { + return (int) (size() * getBucketDuration() / newBucketDuration); + } + + /** + * Utility methods for interacting with {@link DataInputStream} and + * {@link DataOutputStream}, mostly dealing with writing partial arrays. + */ + public static class DataStreamUtils { + @Deprecated + public static long[] readFullLongArray(DataInput in) throws IOException { + final int size = in.readInt(); + if (size < 0) throw new ProtocolException("negative array size"); + final long[] values = new long[size]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readLong(); + } + return values; + } + + /** + * Read variable-length {@link Long} using protobuf-style approach. + */ + public static long readVarLong(DataInput in) throws IOException { + int shift = 0; + long result = 0; + while (shift < 64) { + byte b = in.readByte(); + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) + return result; + shift += 7; + } + throw new ProtocolException("malformed long"); + } + + /** + * Write variable-length {@link Long} using protobuf-style approach. + */ + public static void writeVarLong(DataOutput out, long value) throws IOException { + while (true) { + if ((value & ~0x7FL) == 0) { + out.writeByte((int) value); + return; + } else { + out.writeByte(((int) value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + public static long[] readVarLongArray(DataInput in) throws IOException { + final int size = in.readInt(); + if (size == -1) return null; + if (size < 0) throw new ProtocolException("negative array size"); + final long[] values = new long[size]; + for (int i = 0; i < values.length; i++) { + values[i] = readVarLong(in); + } + return values; + } + + public static void writeVarLongArray(DataOutput out, long[] values, int size) + throws IOException { + if (values == null) { + out.writeInt(-1); + return; + } + if (size > values.length) { + throw new IllegalArgumentException("size larger than length"); + } + out.writeInt(size); + for (int i = 0; i < size; i++) { + writeVarLong(out, values[i]); + } + } + } + + /** + * Utility methods for interacting with {@link Parcel} structures, mostly + * dealing with writing partial arrays. + */ + public static class ParcelUtils { + public static long[] readLongArray(Parcel in) { + final int size = in.readInt(); + if (size == -1) return null; + final long[] values = new long[size]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readLong(); + } + return values; + } + + public static void writeLongArray(Parcel out, long[] values, int size) { + if (values == null) { + out.writeInt(-1); + return; + } + if (size > values.length) { + throw new IllegalArgumentException("size larger than length"); + } + out.writeInt(size); + for (int i = 0; i < size; i++) { + out.writeLong(values[i]); + } + } + } + +} diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java new file mode 100644 index 0000000000..c906a13bf4 --- /dev/null +++ b/framework-t/src/android/net/NetworkTemplate.java @@ -0,0 +1,906 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.net; + +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_PROXY; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIFI_P2P; +import static android.net.ConnectivityManager.TYPE_WIMAX; +import static android.net.NetworkIdentity.OEM_NONE; +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; +import static android.net.wifi.WifiInfo.sanitizeSsid; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Annotation.NetworkType; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.BackupUtils; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; +import com.android.net.module.util.NetworkIdentityUtils; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +/** + * Predicate used to match {@link NetworkIdentity}, usually when collecting + * statistics. (It should probably have been named {@code NetworkPredicate}.) + * + * @hide + */ +public class NetworkTemplate implements Parcelable { + private static final String TAG = "NetworkTemplate"; + + /** + * Initial Version of the backup serializer. + */ + public static final int BACKUP_VERSION_1_INIT = 1; + /** + * Version of the backup serializer that added carrier template support. + */ + public static final int BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2; + /** + * Current Version of the Backup Serializer. + */ + private static final int BACKUP_VERSION = BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE; + + public static final int MATCH_MOBILE = 1; + public static final int MATCH_WIFI = 4; + public static final int MATCH_ETHERNET = 5; + public static final int MATCH_MOBILE_WILDCARD = 6; + public static final int MATCH_WIFI_WILDCARD = 7; + public static final int MATCH_BLUETOOTH = 8; + public static final int MATCH_PROXY = 9; + public static final int MATCH_CARRIER = 10; + + /** + * Value of the match rule of the subscriberId to match networks with specific subscriberId. + */ + public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0; + /** + * Value of the match rule of the subscriberId to match networks with any subscriberId which + * includes null and non-null. + */ + public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1; + + /** + * Wi-Fi Network ID is never supposed to be null (if it is, it is a bug that + * should be fixed), so it's not possible to want to match null vs + * non-null. Therefore it's fine to use null as a sentinel for Network ID. + */ + public static final String WIFI_NETWORKID_ALL = null; + + /** + * Include all network types when filtering. This is meant to merge in with the + * {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync. + * + * @hide + */ + public static final int NETWORK_TYPE_ALL = -1; + /** + * Virtual RAT type to represent 5G NSA (Non Stand Alone) mode, where the primary cell is + * still LTE and network allocates a secondary 5G cell so telephony reports RAT = LTE along + * with NR state as connected. This should not be overlapped with any of the + * {@code TelephonyManager.NETWORK_TYPE_*} constants. + * + * @hide + */ + public static final int NETWORK_TYPE_5G_NSA = -2; + + /** + * Value to match both OEM managed and unmanaged networks (all networks). + * @hide + */ + public static final int OEM_MANAGED_ALL = -1; + /** + * Value to match networks which are not OEM managed. + * @hide + */ + public static final int OEM_MANAGED_NO = OEM_NONE; + /** + * Value to match any OEM managed network. + * @hide + */ + public static final int OEM_MANAGED_YES = -2; + + private static boolean isKnownMatchRule(final int rule) { + switch (rule) { + case MATCH_MOBILE: + case MATCH_WIFI: + case MATCH_ETHERNET: + case MATCH_MOBILE_WILDCARD: + case MATCH_WIFI_WILDCARD: + case MATCH_BLUETOOTH: + case MATCH_PROXY: + case MATCH_CARRIER: + return true; + + default: + return false; + } + } + + /** + * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with + * the given IMSI. + */ + @UnsupportedAppUsage + public static NetworkTemplate buildTemplateMobileAll(String subscriberId) { + return new NetworkTemplate(MATCH_MOBILE, subscriberId, null); + } + + /** + * Template to match cellular networks with the given IMSI, {@code ratType} and + * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when + * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}. + */ + public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId, + @NetworkType int ratType, int metered) { + if (TextUtils.isEmpty(subscriberId)) { + return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null, + metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null, + metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + + /** + * Template to match metered {@link ConnectivityManager#TYPE_MOBILE} networks, + * regardless of IMSI. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static NetworkTemplate buildTemplateMobileWildcard() { + return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null); + } + + /** + * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks, + * regardless of SSID. + */ + @UnsupportedAppUsage + public static NetworkTemplate buildTemplateWifiWildcard() { + // TODO: Consider replace this with MATCH_WIFI with NETWORK_ID_ALL + // and SUBSCRIBER_ID_MATCH_RULE_ALL. + return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null); + } + + @Deprecated + @UnsupportedAppUsage + public static NetworkTemplate buildTemplateWifi() { + return buildTemplateWifiWildcard(); + } + + /** + * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the + * given SSID. + */ + public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) { + Objects.requireNonNull(networkId); + return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */, + new String[] { null } /* matchSubscriberIds */, + networkId, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_ALL); + } + + /** + * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID, + * and IMSI. + * + * Call with {@link #WIFI_NETWORKID_ALL} for {@code networkId} to get result regardless of SSID. + */ + public static NetworkTemplate buildTemplateWifi(@Nullable String networkId, + @Nullable String subscriberId) { + return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId }, + networkId, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + + /** + * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style + * networks together. + */ + @UnsupportedAppUsage + public static NetworkTemplate buildTemplateEthernet() { + return new NetworkTemplate(MATCH_ETHERNET, null, null); + } + + /** + * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style + * networks together. + */ + public static NetworkTemplate buildTemplateBluetooth() { + return new NetworkTemplate(MATCH_BLUETOOTH, null, null); + } + + /** + * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style + * networks together. + */ + public static NetworkTemplate buildTemplateProxy() { + return new NetworkTemplate(MATCH_PROXY, null, null); + } + + /** + * Template to match all metered carrier networks with the given IMSI. + */ + public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) { + Objects.requireNonNull(subscriberId); + return new NetworkTemplate(MATCH_CARRIER, subscriberId, + new String[] { subscriberId }, null /* networkId */, METERED_YES, ROAMING_ALL, + DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + + private final int mMatchRule; + private final String mSubscriberId; + + /** + * Ugh, templates are designed to target a single subscriber, but we might + * need to match several "merged" subscribers. These are the subscribers + * that should be considered to match this template. + * <p> + * Since the merge set is dynamic, it should <em>not</em> be persisted or + * used for determining equality. + */ + private final String[] mMatchSubscriberIds; + + private final String mNetworkId; + + // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. + private final int mMetered; + private final int mRoaming; + private final int mDefaultNetwork; + private final int mSubType; + /** + * The subscriber Id match rule defines how the template should match networks with + * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail. + */ + private final int mSubscriberIdMatchRule; + + // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}. + private final int mOemManaged; + + private void checkValidSubscriberIdMatchRule() { + switch (mMatchRule) { + case MATCH_MOBILE: + case MATCH_CARRIER: + // MOBILE and CARRIER templates must always specify a subscriber ID. + if (mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) { + throw new IllegalArgumentException("Invalid SubscriberIdMatchRule" + + "on match rule: " + getMatchRuleName(mMatchRule)); + } + return; + default: + return; + } + } + + // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S) + @UnsupportedAppUsage + public NetworkTemplate(int matchRule, String subscriberId, String networkId) { + this(matchRule, subscriberId, new String[] { subscriberId }, networkId); + } + + public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, + String networkId) { + // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates + // to metered networks. It is now possible to match mobile with any meteredness, but + // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this + //constructor passes METERED_YES for these types. + this(matchRule, subscriberId, matchSubscriberIds, networkId, + (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES + : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, + OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + + // TODO: Remove it after updating all of the caller. + public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, + String networkId, int metered, int roaming, int defaultNetwork, int subType, + int oemManaged) { + this(matchRule, subscriberId, matchSubscriberIds, networkId, metered, roaming, + defaultNetwork, subType, oemManaged, SUBSCRIBER_ID_MATCH_RULE_EXACT); + } + + public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, + String networkId, int metered, int roaming, int defaultNetwork, int subType, + int oemManaged, int subscriberIdMatchRule) { + mMatchRule = matchRule; + mSubscriberId = subscriberId; + // TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when + // mSubscriberId is null + mMatchSubscriberIds = matchSubscriberIds; + mNetworkId = networkId; + mMetered = metered; + mRoaming = roaming; + mDefaultNetwork = defaultNetwork; + mSubType = subType; + mOemManaged = oemManaged; + mSubscriberIdMatchRule = subscriberIdMatchRule; + checkValidSubscriberIdMatchRule(); + if (!isKnownMatchRule(matchRule)) { + throw new IllegalArgumentException("Unknown network template rule " + matchRule + + " will not match any identity."); + } + } + + private NetworkTemplate(Parcel in) { + mMatchRule = in.readInt(); + mSubscriberId = in.readString(); + mMatchSubscriberIds = in.createStringArray(); + mNetworkId = in.readString(); + mMetered = in.readInt(); + mRoaming = in.readInt(); + mDefaultNetwork = in.readInt(); + mSubType = in.readInt(); + mOemManaged = in.readInt(); + mSubscriberIdMatchRule = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMatchRule); + dest.writeString(mSubscriberId); + dest.writeStringArray(mMatchSubscriberIds); + dest.writeString(mNetworkId); + dest.writeInt(mMetered); + dest.writeInt(mRoaming); + dest.writeInt(mDefaultNetwork); + dest.writeInt(mSubType); + dest.writeInt(mOemManaged); + dest.writeInt(mSubscriberIdMatchRule); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("NetworkTemplate: "); + builder.append("matchRule=").append(getMatchRuleName(mMatchRule)); + if (mSubscriberId != null) { + builder.append(", subscriberId=").append( + NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); + } + if (mMatchSubscriberIds != null) { + builder.append(", matchSubscriberIds=").append( + Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds))); + } + if (mNetworkId != null) { + builder.append(", networkId=").append(mNetworkId); + } + if (mMetered != METERED_ALL) { + builder.append(", metered=").append(NetworkStats.meteredToString(mMetered)); + } + if (mRoaming != ROAMING_ALL) { + builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming)); + } + if (mDefaultNetwork != DEFAULT_NETWORK_ALL) { + builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( + mDefaultNetwork)); + } + if (mSubType != NETWORK_TYPE_ALL) { + builder.append(", subType=").append(mSubType); + } + if (mOemManaged != OEM_MANAGED_ALL) { + builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); + } + builder.append(", subscriberIdMatchRule=") + .append(subscriberIdMatchRuleToString(mSubscriberIdMatchRule)); + return builder.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming, + mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof NetworkTemplate) { + final NetworkTemplate other = (NetworkTemplate) obj; + return mMatchRule == other.mMatchRule + && Objects.equals(mSubscriberId, other.mSubscriberId) + && Objects.equals(mNetworkId, other.mNetworkId) + && mMetered == other.mMetered + && mRoaming == other.mRoaming + && mDefaultNetwork == other.mDefaultNetwork + && mSubType == other.mSubType + && mOemManaged == other.mOemManaged + && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule; + } + return false; + } + + private String subscriberIdMatchRuleToString(int rule) { + switch (rule) { + case SUBSCRIBER_ID_MATCH_RULE_EXACT: + return "EXACT_MATCH"; + case SUBSCRIBER_ID_MATCH_RULE_ALL: + return "ALL"; + default: + return "Unknown rule " + rule; + } + } + + public boolean isMatchRuleMobile() { + switch (mMatchRule) { + case MATCH_MOBILE: + case MATCH_MOBILE_WILDCARD: + return true; + default: + return false; + } + } + + public boolean isPersistable() { + switch (mMatchRule) { + case MATCH_MOBILE_WILDCARD: + case MATCH_WIFI_WILDCARD: + return false; + case MATCH_CARRIER: + return mSubscriberId != null; + case MATCH_WIFI: + if (Objects.equals(mNetworkId, WIFI_NETWORKID_ALL) + && mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) { + return false; + } + return true; + default: + return true; + } + } + + @UnsupportedAppUsage + public int getMatchRule() { + return mMatchRule; + } + + @UnsupportedAppUsage + public String getSubscriberId() { + return mSubscriberId; + } + + public String getNetworkId() { + return mNetworkId; + } + + public int getSubscriberIdMatchRule() { + return mSubscriberIdMatchRule; + } + + public int getMeteredness() { + return mMetered; + } + + /** + * Test if given {@link NetworkIdentity} matches this template. + */ + public boolean matches(NetworkIdentity ident) { + if (!matchesMetered(ident)) return false; + if (!matchesRoaming(ident)) return false; + if (!matchesDefaultNetwork(ident)) return false; + if (!matchesOemNetwork(ident)) return false; + + switch (mMatchRule) { + case MATCH_MOBILE: + return matchesMobile(ident); + case MATCH_WIFI: + return matchesWifi(ident); + case MATCH_ETHERNET: + return matchesEthernet(ident); + case MATCH_MOBILE_WILDCARD: + return matchesMobileWildcard(ident); + case MATCH_WIFI_WILDCARD: + return matchesWifiWildcard(ident); + case MATCH_BLUETOOTH: + return matchesBluetooth(ident); + case MATCH_PROXY: + return matchesProxy(ident); + case MATCH_CARRIER: + return matchesCarrier(ident); + default: + // We have no idea what kind of network template we are, so we + // just claim not to match anything. + return false; + } + } + + private boolean matchesMetered(NetworkIdentity ident) { + return (mMetered == METERED_ALL) + || (mMetered == METERED_YES && ident.mMetered) + || (mMetered == METERED_NO && !ident.mMetered); + } + + private boolean matchesRoaming(NetworkIdentity ident) { + return (mRoaming == ROAMING_ALL) + || (mRoaming == ROAMING_YES && ident.mRoaming) + || (mRoaming == ROAMING_NO && !ident.mRoaming); + } + + private boolean matchesDefaultNetwork(NetworkIdentity ident) { + return (mDefaultNetwork == DEFAULT_NETWORK_ALL) + || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork) + || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork); + } + + private boolean matchesOemNetwork(NetworkIdentity ident) { + return (mOemManaged == OEM_MANAGED_ALL) + || (mOemManaged == OEM_MANAGED_YES + && ident.mOemManaged != OEM_NONE) + || (mOemManaged == ident.mOemManaged); + } + + private boolean matchesCollapsedRatType(NetworkIdentity ident) { + return mSubType == NETWORK_TYPE_ALL + || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType); + } + + /** + * Check if this template matches {@code subscriberId}. Returns true if this + * template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a + * {@code mMatchSubscriberIds} array that contains {@code subscriberId}. + */ + public boolean matchesSubscriberId(@Nullable String subscriberId) { + return mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL + || ArrayUtils.contains(mMatchSubscriberIds, subscriberId); + } + + /** + * Check if network with matching SSID. Returns true when the SSID matches, or when + * {@code mNetworkId} is {@code WIFI_NETWORKID_ALL}. + */ + private boolean matchesWifiNetworkId(@Nullable String networkId) { + return Objects.equals(mNetworkId, WIFI_NETWORKID_ALL) + || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId)); + } + + /** + * Check if mobile network with matching IMSI. + */ + private boolean matchesMobile(NetworkIdentity ident) { + if (ident.mType == TYPE_WIMAX) { + // TODO: consider matching against WiMAX subscriber identity + return true; + } else { + return ident.mType == TYPE_MOBILE && !ArrayUtils.isEmpty(mMatchSubscriberIds) + && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId) + && matchesCollapsedRatType(ident); + } + } + + /** + * Get a Radio Access Technology(RAT) type that is representative of a group of RAT types. + * The mapping is corresponding to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}. + * + * @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}. + */ + // TODO: 1. Consider move this to TelephonyManager if used by other modules. + // 2. Consider make this configurable. + // 3. Use TelephonyManager APIs when available. + public static int getCollapsedRatType(int ratType) { + switch (ratType) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_IDEN: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + return TelephonyManager.NETWORK_TYPE_GSM; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: + return TelephonyManager.NETWORK_TYPE_UMTS; + case TelephonyManager.NETWORK_TYPE_LTE: + case TelephonyManager.NETWORK_TYPE_IWLAN: + return TelephonyManager.NETWORK_TYPE_LTE; + case TelephonyManager.NETWORK_TYPE_NR: + return TelephonyManager.NETWORK_TYPE_NR; + // Virtual RAT type for 5G NSA mode, see {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}. + case NetworkTemplate.NETWORK_TYPE_5G_NSA: + return NetworkTemplate.NETWORK_TYPE_5G_NSA; + default: + return TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Return all supported collapsed RAT types that could be returned by + * {@link #getCollapsedRatType(int)}. + */ + @NonNull + public static final int[] getAllCollapsedRatTypes() { + final int[] ratTypes = TelephonyManager.getAllNetworkTypes(); + final HashSet<Integer> collapsedRatTypes = new HashSet<>(); + for (final int ratType : ratTypes) { + collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(ratType)); + } + // Add NETWORK_TYPE_5G_NSA to the returned list since 5G NSA is a virtual RAT type and + // it is not in TelephonyManager#NETWORK_TYPE_* constants. + // See {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}. + collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(NETWORK_TYPE_5G_NSA)); + // Ensure that unknown type is returned. + collapsedRatTypes.add(TelephonyManager.NETWORK_TYPE_UNKNOWN); + return toIntArray(collapsedRatTypes); + } + + @NonNull + private static int[] toIntArray(@NonNull Collection<Integer> list) { + final int[] array = new int[list.size()]; + int i = 0; + for (final Integer item : list) { + array[i++] = item; + } + return array; + } + + /** + * Check if matches Wi-Fi network template. + */ + private boolean matchesWifi(NetworkIdentity ident) { + switch (ident.mType) { + case TYPE_WIFI: + return matchesSubscriberId(ident.mSubscriberId) + && matchesWifiNetworkId(ident.mNetworkId); + default: + return false; + } + } + + /** + * Check if matches Ethernet network template. + */ + private boolean matchesEthernet(NetworkIdentity ident) { + if (ident.mType == TYPE_ETHERNET) { + return true; + } + return false; + } + + /** + * Check if matches carrier network. The carrier networks means it includes the subscriberId. + */ + private boolean matchesCarrier(NetworkIdentity ident) { + return ident.mSubscriberId != null + && !ArrayUtils.isEmpty(mMatchSubscriberIds) + && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId); + } + + private boolean matchesMobileWildcard(NetworkIdentity ident) { + if (ident.mType == TYPE_WIMAX) { + return true; + } else { + return ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident); + } + } + + private boolean matchesWifiWildcard(NetworkIdentity ident) { + switch (ident.mType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; + } + } + + /** + * Check if matches Bluetooth network template. + */ + private boolean matchesBluetooth(NetworkIdentity ident) { + if (ident.mType == TYPE_BLUETOOTH) { + return true; + } + return false; + } + + /** + * Check if matches Proxy network template. + */ + private boolean matchesProxy(NetworkIdentity ident) { + return ident.mType == TYPE_PROXY; + } + + private static String getMatchRuleName(int matchRule) { + switch (matchRule) { + case MATCH_MOBILE: + return "MOBILE"; + case MATCH_WIFI: + return "WIFI"; + case MATCH_ETHERNET: + return "ETHERNET"; + case MATCH_MOBILE_WILDCARD: + return "MOBILE_WILDCARD"; + case MATCH_WIFI_WILDCARD: + return "WIFI_WILDCARD"; + case MATCH_BLUETOOTH: + return "BLUETOOTH"; + case MATCH_PROXY: + return "PROXY"; + case MATCH_CARRIER: + return "CARRIER"; + default: + return "UNKNOWN(" + matchRule + ")"; + } + } + + private static String getOemManagedNames(int oemManaged) { + switch (oemManaged) { + case OEM_MANAGED_ALL: + return "OEM_MANAGED_ALL"; + case OEM_MANAGED_NO: + return "OEM_MANAGED_NO"; + case OEM_MANAGED_YES: + return "OEM_MANAGED_YES"; + default: + return NetworkIdentity.getOemManagedNames(oemManaged); + } + } + + /** + * Examine the given template and normalize it. + * We pick the "lowest" merged subscriber as the primary + * for key purposes, and expand the template to match all other merged + * subscribers. + * <p> + * For example, given an incoming template matching B, and the currently + * active merge set [A,B], we'd return a new template that primarily matches + * A, but also matches B. + * TODO: remove and use {@link #normalize(NetworkTemplate, List)}. + */ + @UnsupportedAppUsage + public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) { + return normalize(template, Arrays.<String[]>asList(merged)); + } + + /** + * Examine the given template and normalize it. + * We pick the "lowest" merged subscriber as the primary + * for key purposes, and expand the template to match all other merged + * subscribers. + * + * There can be multiple merged subscriberIds for multi-SIM devices. + * + * <p> + * For example, given an incoming template matching B, and the currently + * active merge set [A,B], we'd return a new template that primarily matches + * A, but also matches B. + */ + public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) { + // Now there are several types of network which uses SubscriberId to store network + // information. For instances: + // The TYPE_WIFI with subscriberId means that it is a merged carrier wifi network. + // The TYPE_CARRIER means that the network associate to specific carrier network. + + if (template.mSubscriberId == null) return template; + + for (String[] merged : mergedList) { + if (ArrayUtils.contains(merged, template.mSubscriberId)) { + // Requested template subscriber is part of the merge group; return + // a template that matches all merged subscribers. + return new NetworkTemplate(template.mMatchRule, merged[0], merged, + template.mNetworkId); + } + } + + return template; + } + + @UnsupportedAppUsage + public static final @android.annotation.NonNull Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() { + @Override + public NetworkTemplate createFromParcel(Parcel in) { + return new NetworkTemplate(in); + } + + @Override + public NetworkTemplate[] newArray(int size) { + return new NetworkTemplate[size]; + } + }; + + public byte[] getBytesForBackup() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(baos); + + if (!isPersistable()) { + Log.wtf(TAG, "Trying to backup non-persistable template: " + this); + } + + out.writeInt(BACKUP_VERSION); + + out.writeInt(mMatchRule); + BackupUtils.writeString(out, mSubscriberId); + BackupUtils.writeString(out, mNetworkId); + out.writeInt(mMetered); + out.writeInt(mSubscriberIdMatchRule); + + return baos.toByteArray(); + } + + public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in) + throws IOException, BackupUtils.BadVersionException { + int version = in.readInt(); + if (version < BACKUP_VERSION_1_INIT || version > BACKUP_VERSION) { + throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version"); + } + + int matchRule = in.readInt(); + String subscriberId = BackupUtils.readString(in); + String networkId = BackupUtils.readString(in); + + final int metered; + final int subscriberIdMatchRule; + if (version >= BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) { + metered = in.readInt(); + subscriberIdMatchRule = in.readInt(); + } else { + // For backward compatibility, fill the missing filters from match rules. + metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD + || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL; + subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT; + } + + try { + return new NetworkTemplate(matchRule, + subscriberId, new String[] { subscriberId }, + networkId, metered, NetworkStats.ROAMING_ALL, + NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL, + NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule); + } catch (IllegalArgumentException e) { + throw new BackupUtils.BadVersionException( + "Restored network template contains unknown match rule " + matchRule, e); + } + } +} diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java new file mode 100644 index 0000000000..fa650617f3 --- /dev/null +++ b/framework-t/src/android/net/TrafficStats.java @@ -0,0 +1,1032 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.app.DownloadManager; +import android.app.backup.BackupManager; +import android.app.usage.NetworkStatsManager; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.media.MediaPlayer; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.DataUnit; + +import com.android.server.NetworkManagementSocketTagger; + +import dalvik.system.SocketTagger; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.Socket; +import java.net.SocketException; + +/** + * 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. + * <p> + * These statistics may not be available on all platforms. If the statistics are + * not supported by this device, {@link #UNSUPPORTED} will be returned. + * <p> + * Note that the statistics returned by this class reset and start from zero + * after every reboot. To access more robust historical network statistics data, + * use {@link NetworkStatsManager} instead. + */ +public class TrafficStats { + /** + * The return value to indicate that the device does not support the statistic. + */ + public final static int UNSUPPORTED = -1; + + /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + @Deprecated + public static final long KB_IN_BYTES = 1024; + /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + @Deprecated + public static final long MB_IN_BYTES = KB_IN_BYTES * 1024; + /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + @Deprecated + public static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + @Deprecated + public static final long TB_IN_BYTES = GB_IN_BYTES * 1024; + /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + @Deprecated + public static final long PB_IN_BYTES = TB_IN_BYTES * 1024; + + /** + * Special UID value used when collecting {@link NetworkStatsHistory} for + * removed applications. + * + * @hide + */ + public static final int UID_REMOVED = -4; + + /** + * Special UID value used when collecting {@link NetworkStatsHistory} for + * tethering traffic. + * + * @hide + */ + public static final int UID_TETHERING = NetworkStats.UID_TETHERING; + + /** + * Tag values in this range are reserved for the network stack. The network stack is + * running as UID {@link android.os.Process.NETWORK_STACK_UID} when in the mainline + * module separate process, and as the system UID otherwise. + */ + /** @hide */ + @SystemApi + public static final int TAG_NETWORK_STACK_RANGE_START = 0xFFFFFD00; + /** @hide */ + @SystemApi + public static final int TAG_NETWORK_STACK_RANGE_END = 0xFFFFFEFF; + + /** + * Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services + * like DownloadManager when performing traffic on behalf of an application. + */ + // Please note there is no enforcement of these constants, so do not rely on them to + // determine that the caller is a system caller. + /** @hide */ + @SystemApi + public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = 0xFFFFFF00; + /** @hide */ + @SystemApi + public static final int TAG_SYSTEM_IMPERSONATION_RANGE_END = 0xFFFFFF0F; + + /** + * Tag values between these ranges are reserved for the network stack to do traffic + * on behalf of applications. It is a subrange of the range above. + */ + /** @hide */ + @SystemApi + public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = 0xFFFFFF80; + /** @hide */ + @SystemApi + public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = 0xFFFFFF8F; + + /** + * Default tag value for {@link DownloadManager} traffic. + * + * @hide + */ + public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01; + + /** + * Default tag value for {@link MediaPlayer} traffic. + * + * @hide + */ + public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02; + + /** + * Default tag value for {@link BackupManager} backup traffic; that is, + * traffic from the device to the storage backend. + * + * @hide + */ + public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03; + + /** + * Default tag value for {@link BackupManager} restore traffic; that is, + * app data retrieved from the storage backend at install time. + * + * @hide + */ + public static final int TAG_SYSTEM_RESTORE = 0xFFFFFF04; + + /** + * Default tag value for code (typically APKs) downloaded by an app store on + * behalf of the app, such as updates. + * + * @hide + */ + public static final int TAG_SYSTEM_APP = 0xFFFFFF05; + + // TODO : remove this constant when Wifi code is updated + /** @hide */ + public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42; + + private static INetworkStatsService sStatsService; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + private synchronized static INetworkStatsService getStatsService() { + if (sStatsService == null) { + sStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + } + return sStatsService; + } + + /** + * Snapshot of {@link NetworkStats} when the currently active profiling + * session started, or {@code null} if no session active. + * + * @see #startDataProfiling(Context) + * @see #stopDataProfiling(Context) + */ + private static NetworkStats sActiveProfilingStart; + + private static Object sProfilingLock = new Object(); + + private static final String LOOPBACK_IFACE = "lo"; + + /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. Only one active tag per thread is supported. + * <p> + * Changes only take effect during subsequent calls to + * {@link #tagSocket(Socket)}. + * <p> + * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and + * used internally by system services like {@link DownloadManager} when + * performing traffic on behalf of an application. + * + * @see #clearThreadStatsTag() + */ + public static void setThreadStatsTag(int tag) { + NetworkManagementSocketTagger.setThreadSocketStatsTag(tag); + } + + /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. Only one active tag per thread is supported. + * <p> + * Changes only take effect during subsequent calls to + * {@link #tagSocket(Socket)}. + * <p> + * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and + * used internally by system services like {@link DownloadManager} when + * performing traffic on behalf of an application. + * + * @return the current tag for the calling thread, which can be used to + * restore any existing values after a nested operation is finished + */ + public static int getAndSetThreadStatsTag(int tag) { + return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag); + } + + /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. The tag used internally is well-defined to + * distinguish all backup-related traffic. + * + * @hide + */ + @SystemApi + public static void setThreadStatsTagBackup() { + setThreadStatsTag(TAG_SYSTEM_BACKUP); + } + + /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. The tag used internally is well-defined to + * distinguish all restore-related traffic. + * + * @hide + */ + @SystemApi + public static void setThreadStatsTagRestore() { + setThreadStatsTag(TAG_SYSTEM_RESTORE); + } + + /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. The tag used internally is well-defined to + * distinguish all code (typically APKs) downloaded by an app store on + * behalf of the app, such as updates. + * + * @hide + */ + @SystemApi + public static void setThreadStatsTagApp() { + setThreadStatsTag(TAG_SYSTEM_APP); + } + + /** + * Get the active tag used when accounting {@link Socket} traffic originating + * from the current thread. Only one active tag per thread is supported. + * {@link #tagSocket(Socket)}. + * + * @see #setThreadStatsTag(int) + */ + public static int getThreadStatsTag() { + return NetworkManagementSocketTagger.getThreadSocketStatsTag(); + } + + /** + * Clear any active tag set to account {@link Socket} traffic originating + * from the current thread. + * + * @see #setThreadStatsTag(int) + */ + public static void clearThreadStatsTag() { + NetworkManagementSocketTagger.setThreadSocketStatsTag(-1); + } + + /** + * Set specific UID to use when accounting {@link Socket} traffic + * originating from the current thread. Designed for use when performing an + * operation on behalf of another application, or when another application + * is performing operations on your behalf. + * <p> + * Any app can <em>accept</em> blame for traffic performed on a socket + * originally created by another app by calling this method with the + * {@link android.system.Os#getuid()} value. However, only apps holding the + * {@code android.Manifest.permission#UPDATE_DEVICE_STATS} permission may + * <em>assign</em> blame to another UIDs. + * <p> + * Changes only take effect during subsequent calls to + * {@link #tagSocket(Socket)}. + */ + @SuppressLint("RequiresPermission") + public static void setThreadStatsUid(int uid) { + NetworkManagementSocketTagger.setThreadSocketStatsUid(uid); + } + + /** + * Get the active UID used when accounting {@link Socket} traffic originating + * from the current thread. Only one active tag per thread is supported. + * {@link #tagSocket(Socket)}. + * + * @see #setThreadStatsUid(int) + */ + public static int getThreadStatsUid() { + return NetworkManagementSocketTagger.getThreadSocketStatsUid(); + } + + /** + * Set specific UID to use when accounting {@link Socket} traffic + * originating from the current thread as the calling UID. Designed for use + * when another application is performing operations on your behalf. + * <p> + * Changes only take effect during subsequent calls to + * {@link #tagSocket(Socket)}. + * + * @removed + * @deprecated use {@link #setThreadStatsUid(int)} instead. + */ + @Deprecated + public static void setThreadStatsUidSelf() { + setThreadStatsUid(android.os.Process.myUid()); + } + + /** + * Clear any active UID set to account {@link Socket} traffic originating + * from the current thread. + * + * @see #setThreadStatsUid(int) + */ + @SuppressLint("RequiresPermission") + public static void clearThreadStatsUid() { + NetworkManagementSocketTagger.setThreadSocketStatsUid(-1); + } + + /** + * Tag the given {@link Socket} with any statistics parameters active for + * the current thread. Subsequent calls always replace any existing + * parameters. When finished, call {@link #untagSocket(Socket)} to remove + * statistics parameters. + * + * @see #setThreadStatsTag(int) + */ + public static void tagSocket(Socket socket) throws SocketException { + SocketTagger.get().tag(socket); + } + + /** + * Remove any statistics parameters from the given {@link Socket}. + * <p> + * In Android 8.1 (API level 27) and lower, a socket is automatically + * untagged when it's sent to another process using binder IPC with a + * {@code ParcelFileDescriptor} container. In Android 9.0 (API level 28) + * and higher, the socket tag is kept when the socket is sent to another + * process using binder IPC. You can mimic the previous behavior by + * calling {@code untagSocket()} before sending the socket to another + * process. + */ + public static void untagSocket(Socket socket) throws SocketException { + SocketTagger.get().untag(socket); + } + + /** + * Tag the given {@link DatagramSocket} with any statistics parameters + * active for the current thread. Subsequent calls always replace any + * existing parameters. When finished, call + * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics + * parameters. + * + * @see #setThreadStatsTag(int) + */ + public static void tagDatagramSocket(DatagramSocket socket) throws SocketException { + SocketTagger.get().tag(socket); + } + + /** + * Remove any statistics parameters from the given {@link DatagramSocket}. + */ + public static void untagDatagramSocket(DatagramSocket socket) throws SocketException { + SocketTagger.get().untag(socket); + } + + /** + * Tag the given {@link FileDescriptor} socket with any statistics + * parameters active for the current thread. Subsequent calls always replace + * any existing parameters. When finished, call + * {@link #untagFileDescriptor(FileDescriptor)} to remove statistics + * parameters. + * + * @see #setThreadStatsTag(int) + */ + public static void tagFileDescriptor(FileDescriptor fd) throws IOException { + SocketTagger.get().tag(fd); + } + + /** + * Remove any statistics parameters from the given {@link FileDescriptor} + * socket. + */ + public static void untagFileDescriptor(FileDescriptor fd) throws IOException { + SocketTagger.get().untag(fd); + } + + /** + * Start profiling data usage for current UID. Only one profiling session + * can be active at a time. + * + * @hide + */ + public static void startDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart != null) { + throw new IllegalStateException("already profiling data"); + } + + // take snapshot in time; we calculate delta later + sActiveProfilingStart = getDataLayerSnapshotForUid(context); + } + } + + /** + * Stop profiling data usage for current UID. + * + * @return Detailed {@link NetworkStats} of data that occurred since last + * {@link #startDataProfiling(Context)} call. + * @hide + */ + public static NetworkStats stopDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart == null) { + throw new IllegalStateException("not profiling data"); + } + + // subtract starting values and return delta + final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); + final NetworkStats profilingDelta = NetworkStats.subtract( + profilingStop, sActiveProfilingStart, null, null); + sActiveProfilingStart = null; + return profilingDelta; + } + } + + /** + * Increment count of network operations performed under the accounting tag + * currently active on the calling thread. This can be used to derive + * bytes-per-operation. + * + * @param operationCount Number of operations to increment count by. + */ + public static void incrementOperationCount(int operationCount) { + final int tag = getThreadStatsTag(); + incrementOperationCount(tag, operationCount); + } + + /** + * Increment count of network operations performed under the given + * accounting tag. This can be used to derive bytes-per-operation. + * + * @param tag Accounting tag used in {@link #setThreadStatsTag(int)}. + * @param operationCount Number of operations to increment count by. + */ + public static void incrementOperationCount(int tag, int operationCount) { + final int uid = android.os.Process.myUid(); + try { + getStatsService().incrementOperationCount(uid, tag, operationCount); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + public static void closeQuietly(INetworkStatsSession session) { + // TODO: move to NetworkStatsService once it exists + if (session != null) { + try { + session.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + private static long addIfSupported(long stat) { + return (stat == UNSUPPORTED) ? 0 : stat; + } + + /** + * Return number of packets transmitted across mobile networks since device + * boot. Counts packets across all mobile network interfaces, and always + * increases monotonically since device boot. Statistics are measured at the + * network layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getMobileTxPackets() { + long total = 0; + for (String iface : getMobileIfaces()) { + total += addIfSupported(getTxPackets(iface)); + } + return total; + } + + /** + * Return number of packets received across mobile networks since device + * boot. Counts packets across all mobile network interfaces, and always + * increases monotonically since device boot. Statistics are measured at the + * network layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getMobileRxPackets() { + long total = 0; + for (String iface : getMobileIfaces()) { + total += addIfSupported(getRxPackets(iface)); + } + return total; + } + + /** + * Return number of bytes transmitted across mobile networks since device + * boot. Counts packets across all mobile network interfaces, and always + * increases monotonically since device boot. Statistics are measured at the + * network layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getMobileTxBytes() { + long total = 0; + for (String iface : getMobileIfaces()) { + total += addIfSupported(getTxBytes(iface)); + } + return total; + } + + /** + * Return number of bytes received across mobile networks since device boot. + * Counts packets across all mobile network interfaces, and always increases + * monotonically since device boot. Statistics are measured at the network + * layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getMobileRxBytes() { + long total = 0; + for (String iface : getMobileIfaces()) { + total += addIfSupported(getRxBytes(iface)); + } + return total; + } + + /** {@hide} */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static long getMobileTcpRxPackets() { + long total = 0; + for (String iface : getMobileIfaces()) { + long stat = UNSUPPORTED; + try { + stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + total += addIfSupported(stat); + } + return total; + } + + /** {@hide} */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static long getMobileTcpTxPackets() { + long total = 0; + for (String iface : getMobileIfaces()) { + long stat = UNSUPPORTED; + try { + stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + total += addIfSupported(stat); + } + return total; + } + + /** + * Return the number of packets transmitted on the specified interface since the interface + * was created. Statistics are measured at the network layer, so both TCP and + * UDP usage are included. + * + * Note that the returned values are partial statistics that do not count data from several + * sources and do not apply several adjustments that are necessary for correctness, such + * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to + * determine whether traffic is being transferred on the specific interface but are not a + * substitute for the more accurate statistics provided by the {@link NetworkStatsManager} + * APIs. + * + * @param iface The name of the interface. + * @return The number of transmitted packets. + */ + public static long getTxPackets(@NonNull String iface) { + try { + return getStatsService().getIfaceStats(iface, TYPE_TX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the number of packets received on the specified interface since the interface was + * created. Statistics are measured at the network layer, so both TCP + * and UDP usage are included. + * + * Note that the returned values are partial statistics that do not count data from several + * sources and do not apply several adjustments that are necessary for correctness, such + * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to + * determine whether traffic is being transferred on the specific interface but are not a + * substitute for the more accurate statistics provided by the {@link NetworkStatsManager} + * APIs. + * + * @param iface The name of the interface. + * @return The number of received packets. + */ + public static long getRxPackets(@NonNull String iface) { + try { + return getStatsService().getIfaceStats(iface, TYPE_RX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the number of bytes transmitted on the specified interface since the interface + * was created. Statistics are measured at the network layer, so both TCP and + * UDP usage are included. + * + * Note that the returned values are partial statistics that do not count data from several + * sources and do not apply several adjustments that are necessary for correctness, such + * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to + * determine whether traffic is being transferred on the specific interface but are not a + * substitute for the more accurate statistics provided by the {@link NetworkStatsManager} + * APIs. + * + * @param iface The name of the interface. + * @return The number of transmitted bytes. + */ + public static long getTxBytes(@NonNull String iface) { + try { + return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the number of bytes received on the specified interface since the interface + * was created. Statistics are measured at the network layer, so both TCP + * and UDP usage are included. + * + * Note that the returned values are partial statistics that do not count data from several + * sources and do not apply several adjustments that are necessary for correctness, such + * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to + * determine whether traffic is being transferred on the specific interface but are not a + * substitute for the more accurate statistics provided by the {@link NetworkStatsManager} + * APIs. + * + * @param iface The name of the interface. + * @return The number of received bytes. + */ + public static long getRxBytes(@NonNull String iface) { + try { + return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @TestApi + public static long getLoopbackTxPackets() { + try { + return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @TestApi + public static long getLoopbackRxPackets() { + try { + return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @TestApi + public static long getLoopbackTxBytes() { + try { + return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @TestApi + public static long getLoopbackRxBytes() { + try { + return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of packets transmitted since device boot. Counts packets + * across all network interfaces, and always increases monotonically since + * device boot. Statistics are measured at the network layer, so they + * include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getTotalTxPackets() { + try { + return getStatsService().getTotalStats(TYPE_TX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of packets received since device boot. Counts packets + * across all network interfaces, and always increases monotonically since + * device boot. Statistics are measured at the network layer, so they + * include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getTotalRxPackets() { + try { + return getStatsService().getTotalStats(TYPE_RX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of bytes transmitted since device boot. Counts packets + * across all network interfaces, and always increases monotonically since + * device boot. Statistics are measured at the network layer, so they + * include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getTotalTxBytes() { + try { + return getStatsService().getTotalStats(TYPE_TX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of bytes received since device boot. Counts packets across + * all network interfaces, and always increases monotonically since device + * boot. Statistics are measured at the network layer, so they include both + * TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + */ + public static long getTotalRxBytes() { + try { + return getStatsService().getTotalStats(TYPE_RX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of bytes transmitted by the given UID since device boot. + * Counts packets across all network interfaces, and always increases + * monotonically since device boot. Statistics are measured at the network + * layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may + * return {@link #UNSUPPORTED} on devices where statistics aren't available. + * <p> + * Starting in {@link android.os.Build.VERSION_CODES#N} this will only + * report traffic statistics for the calling UID. It will return + * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access + * historical network statistics belonging to other UIDs, use + * {@link NetworkStatsManager}. + * + * @see android.os.Process#myUid() + * @see android.content.pm.ApplicationInfo#uid + */ + public static long getUidTxBytes(int uid) { + try { + return getStatsService().getUidStats(uid, TYPE_TX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of bytes received by the given UID since device boot. + * Counts packets across all network interfaces, and always increases + * monotonically since device boot. Statistics are measured at the network + * layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return + * {@link #UNSUPPORTED} on devices where statistics aren't available. + * <p> + * Starting in {@link android.os.Build.VERSION_CODES#N} this will only + * report traffic statistics for the calling UID. It will return + * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access + * historical network statistics belonging to other UIDs, use + * {@link NetworkStatsManager}. + * + * @see android.os.Process#myUid() + * @see android.content.pm.ApplicationInfo#uid + */ + public static long getUidRxBytes(int uid) { + try { + return getStatsService().getUidStats(uid, TYPE_RX_BYTES); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of packets transmitted by the given UID since device boot. + * Counts packets across all network interfaces, and always increases + * monotonically since device boot. Statistics are measured at the network + * layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return + * {@link #UNSUPPORTED} on devices where statistics aren't available. + * <p> + * Starting in {@link android.os.Build.VERSION_CODES#N} this will only + * report traffic statistics for the calling UID. It will return + * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access + * historical network statistics belonging to other UIDs, use + * {@link NetworkStatsManager}. + * + * @see android.os.Process#myUid() + * @see android.content.pm.ApplicationInfo#uid + */ + public static long getUidTxPackets(int uid) { + try { + return getStatsService().getUidStats(uid, TYPE_TX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return number of packets received by the given UID since device boot. + * Counts packets across all network interfaces, and always increases + * monotonically since device boot. Statistics are measured at the network + * layer, so they include both TCP and UDP usage. + * <p> + * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return + * {@link #UNSUPPORTED} on devices where statistics aren't available. + * <p> + * Starting in {@link android.os.Build.VERSION_CODES#N} this will only + * report traffic statistics for the calling UID. It will return + * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access + * historical network statistics belonging to other UIDs, use + * {@link NetworkStatsManager}. + * + * @see android.os.Process#myUid() + * @see android.content.pm.ApplicationInfo#uid + */ + public static long getUidRxPackets(int uid) { + try { + return getStatsService().getUidStats(uid, TYPE_RX_PACKETS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidTxBytes(int) + */ + @Deprecated + public static long getUidTcpTxBytes(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidRxBytes(int) + */ + @Deprecated + public static long getUidTcpRxBytes(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidTxBytes(int) + */ + @Deprecated + public static long getUidUdpTxBytes(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidRxBytes(int) + */ + @Deprecated + public static long getUidUdpRxBytes(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidTxPackets(int) + */ + @Deprecated + public static long getUidTcpTxSegments(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidRxPackets(int) + */ + @Deprecated + public static long getUidTcpRxSegments(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidTxPackets(int) + */ + @Deprecated + public static long getUidUdpTxPackets(int uid) { + return UNSUPPORTED; + } + + /** + * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, + * transport layer statistics are no longer available, and will + * always return {@link #UNSUPPORTED}. + * @see #getUidRxPackets(int) + */ + @Deprecated + public static long getUidUdpRxPackets(int uid) { + return UNSUPPORTED; + } + + /** + * Return detailed {@link NetworkStats} for the current UID. Requires no + * special permission. + */ + private static NetworkStats getDataLayerSnapshotForUid(Context context) { + // TODO: take snapshot locally, since proc file is now visible + final int uid = android.os.Process.myUid(); + try { + return getStatsService().getDataLayerSnapshotForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return set of any ifaces associated with mobile networks since boot. + * Interfaces are never removed from this list, so counters should always be + * monotonic. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + private static String[] getMobileIfaces() { + try { + return getStatsService().getMobileIfaces(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // NOTE: keep these in sync with {@code com_android_server_net_NetworkStatsService.cpp}. + /** {@hide} */ + public static final int TYPE_RX_BYTES = 0; + /** {@hide} */ + public static final int TYPE_RX_PACKETS = 1; + /** {@hide} */ + public static final int TYPE_TX_BYTES = 2; + /** {@hide} */ + public static final int TYPE_TX_PACKETS = 3; + /** {@hide} */ + public static final int TYPE_TCP_RX_PACKETS = 4; + /** {@hide} */ + public static final int TYPE_TCP_TX_PACKETS = 5; +} diff --git a/framework-t/src/android/net/UnderlyingNetworkInfo.aidl b/framework-t/src/android/net/UnderlyingNetworkInfo.aidl new file mode 100644 index 0000000000..a56f2f4058 --- /dev/null +++ b/framework-t/src/android/net/UnderlyingNetworkInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 android.net; + +parcelable UnderlyingNetworkInfo; diff --git a/framework-t/src/android/net/UnderlyingNetworkInfo.java b/framework-t/src/android/net/UnderlyingNetworkInfo.java new file mode 100644 index 0000000000..33f9375c03 --- /dev/null +++ b/framework-t/src/android/net/UnderlyingNetworkInfo.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 android.net; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A lightweight container used to carry information on the networks that underly a given + * virtual network. + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +public final class UnderlyingNetworkInfo implements Parcelable { + /** The owner of this network. */ + private final int mOwnerUid; + + /** The interface name of this network. */ + @NonNull + private final String mIface; + + /** The names of the interfaces underlying this network. */ + @NonNull + private final List<String> mUnderlyingIfaces; + + public UnderlyingNetworkInfo(int ownerUid, @NonNull String iface, + @NonNull List<String> underlyingIfaces) { + Objects.requireNonNull(iface); + Objects.requireNonNull(underlyingIfaces); + mOwnerUid = ownerUid; + mIface = iface; + mUnderlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces)); + } + + private UnderlyingNetworkInfo(@NonNull Parcel in) { + mOwnerUid = in.readInt(); + mIface = in.readString(); + List<String> underlyingIfaces = new ArrayList<>(); + in.readList(underlyingIfaces, null /*classLoader*/); + mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces); + } + + /** Get the owner of this network. */ + public int getOwnerUid() { + return mOwnerUid; + } + + /** Get the interface name of this network. */ + @NonNull + public String getInterface() { + return mIface; + } + + /** Get the names of the interfaces underlying this network. */ + @NonNull + public List<String> getUnderlyingInterfaces() { + return mUnderlyingIfaces; + } + + @Override + public String toString() { + return "UnderlyingNetworkInfo{" + + "ownerUid=" + mOwnerUid + + ", iface='" + mIface + '\'' + + ", underlyingIfaces='" + mUnderlyingIfaces.toString() + '\'' + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mOwnerUid); + dest.writeString(mIface); + dest.writeList(mUnderlyingIfaces); + } + + @NonNull + public static final Parcelable.Creator<UnderlyingNetworkInfo> CREATOR = + new Parcelable.Creator<UnderlyingNetworkInfo>() { + @NonNull + @Override + public UnderlyingNetworkInfo createFromParcel(@NonNull Parcel in) { + return new UnderlyingNetworkInfo(in); + } + + @NonNull + @Override + public UnderlyingNetworkInfo[] newArray(int size) { + return new UnderlyingNetworkInfo[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UnderlyingNetworkInfo)) return false; + final UnderlyingNetworkInfo that = (UnderlyingNetworkInfo) o; + return mOwnerUid == that.getOwnerUid() + && Objects.equals(mIface, that.getInterface()) + && Objects.equals(mUnderlyingIfaces, that.getUnderlyingInterfaces()); + } + + @Override + public int hashCode() { + return Objects.hash(mOwnerUid, mIface, mUnderlyingIfaces); + } +} diff --git a/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl b/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl new file mode 100644 index 0000000000..74c3ba44b6 --- /dev/null +++ b/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 android.net.netstats.provider; + +/** + * Interface for NetworkStatsService to query network statistics and set data limits. + * + * @hide + */ +oneway interface INetworkStatsProvider { + void onRequestStatsUpdate(int token); + void onSetAlert(long quotaBytes); + void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes); +} diff --git a/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl new file mode 100644 index 0000000000..7eaa01e262 --- /dev/null +++ b/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 android.net.netstats.provider; + +import android.net.NetworkStats; + +/** + * Interface for implementor of {@link INetworkStatsProviderCallback} to push events + * such as network statistics update or notify limit reached. + * @hide + */ +oneway interface INetworkStatsProviderCallback { + void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats); + void notifyAlertReached(); + void notifyWarningOrLimitReached(); + void unregister(); +} diff --git a/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java new file mode 100644 index 0000000000..23fc06927e --- /dev/null +++ b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 android.net.netstats.provider; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.NetworkStats; +import android.os.RemoteException; + +/** + * A base class that allows external modules to implement a custom network statistics provider. + * @hide + */ +@SystemApi +public abstract class NetworkStatsProvider { + /** + * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit} + * indicates there is no limit. + */ + public static final int QUOTA_UNLIMITED = -1; + + @NonNull private final INetworkStatsProvider mProviderBinder = + new INetworkStatsProvider.Stub() { + + @Override + public void onRequestStatsUpdate(int token) { + NetworkStatsProvider.this.onRequestStatsUpdate(token); + } + + @Override + public void onSetAlert(long quotaBytes) { + NetworkStatsProvider.this.onSetAlert(quotaBytes); + } + + @Override + public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) { + NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes); + } + }; + + // The binder given by the service when successfully registering. Only null before registering, + // never null once non-null. + @Nullable + private INetworkStatsProviderCallback mProviderCbBinder; + + /** + * Return the binder invoked by the service and redirect function calls to the overridden + * methods. + * @hide + */ + @NonNull + public INetworkStatsProvider getProviderBinder() { + return mProviderBinder; + } + + /** + * Store the binder that was returned by the service when successfully registering. Note that + * the provider cannot be re-registered. Hence this method can only be called once per provider. + * + * @hide + */ + public void setProviderCallbackBinder(@NonNull INetworkStatsProviderCallback binder) { + if (mProviderCbBinder != null) { + throw new IllegalArgumentException("provider is already registered"); + } + mProviderCbBinder = binder; + } + + /** + * Get the binder that was returned by the service when successfully registering. Or null if the + * provider was never registered. + * + * @hide + */ + @Nullable + public INetworkStatsProviderCallback getProviderCallbackBinder() { + return mProviderCbBinder; + } + + /** + * Get the binder that was returned by the service when successfully registering. Throw an + * {@link IllegalStateException} if the provider is not registered. + * + * @hide + */ + @NonNull + public INetworkStatsProviderCallback getProviderCallbackBinderOrThrow() { + if (mProviderCbBinder == null) { + throw new IllegalStateException("the provider is not registered"); + } + return mProviderCbBinder; + } + + /** + * Notify the system of new network statistics. + * + * Send the network statistics recorded since the last call to {@link #notifyStatsUpdated}. Must + * be called as soon as possible after {@link NetworkStatsProvider#onRequestStatsUpdate(int)} + * being called. Responding later increases the probability stats will be dropped. The + * provider can also call this whenever it wants to reports new stats for any reason. + * Note that the system will not necessarily immediately propagate the statistics to + * reflect the update. + * + * @param token the token under which these stats were gathered. Providers can call this method + * with the current token as often as they want, until the token changes. + * {@see NetworkStatsProvider#onRequestStatsUpdate()} + * @param ifaceStats the {@link NetworkStats} per interface to be reported. + * The provider should not include any traffic that is already counted by + * kernel interface counters. + * @param uidStats the same stats as above, but counts {@link NetworkStats} + * per uid. + */ + public void notifyStatsUpdated(int token, @NonNull NetworkStats ifaceStats, + @NonNull NetworkStats uidStats) { + try { + getProviderCallbackBinderOrThrow().notifyStatsUpdated(token, ifaceStats, uidStats); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@code onSetAlert} has been reached. + */ + public void notifyAlertReached() { + try { + getProviderCallbackBinderOrThrow().notifyAlertReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached. + */ + public void notifyWarningReached() { + try { + // Reuse the code path to notify warning reached with limit reached + // since framework handles them in the same way. + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@link #onSetLimit} or limit set by + * {@link #onSetWarningAndLimit} has been reached. + */ + public void notifyLimitReached() { + try { + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Called by {@code NetworkStatsService} when it requires to know updated stats. + * The provider MUST respond by calling {@link #notifyStatsUpdated} as soon as possible. + * Responding later increases the probability stats will be dropped. Memory allowing, the + * system will try to take stats into account up to one minute after calling + * {@link #onRequestStatsUpdate}. + * + * @param token a positive number identifying the new state of the system under which + * {@link NetworkStats} have to be gathered from now on. When this is called, + * custom implementations of providers MUST tally and report the latest stats with + * the previous token, under which stats were being gathered so far. + */ + public abstract void onRequestStatsUpdate(int token); + + /** + * Called by {@code NetworkStatsService} when setting the interface quota for the specified + * upstream interface. When this is called, the custom implementation should block all egress + * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have + * been reached, and MUST respond to it by calling + * {@link NetworkStatsProvider#notifyLimitReached()}. + * + * @param iface the interface requiring the operation. + * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. + */ + public abstract void onSetLimit(@NonNull String iface, long quotaBytes); + + /** + * Called by {@code NetworkStatsService} when setting the interface quotas for the specified + * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system + * will not call {@link #onSetLimit}. When this method is called, the implementation + * should behave as follows: + * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}. + * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}. + * + * @param iface the interface requiring the operation. + * @param warningBytes the warning defined as the number of bytes, starting from zero and + * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates + * there is no warning. + * @param limitBytes the limit defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. + */ + public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) { + // Backward compatibility for those who didn't override this function. + onSetLimit(iface, limitBytes); + } + + /** + * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations + * MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes + * have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should + * not block all egress packets. + * + * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert. + */ + public abstract void onSetAlert(long quotaBytes); +} |
