content: /**
   * 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;
          }
      }
  }
  