content: /*
   * 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 com.android.server.net;
  
  import static android.Manifest.permission.ACCESS_NETWORK_STATE;
  import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
  import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
  import static android.content.Intent.ACTION_SHUTDOWN;
  import static android.content.Intent.ACTION_UID_REMOVED;
  import static android.content.Intent.ACTION_USER_REMOVED;
  import static android.content.Intent.EXTRA_UID;
  import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
  import static android.net.ConnectivityManager.isNetworkTypeMobile;
  import static android.net.NetworkStack.checkNetworkStackPermission;
  import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
  import static android.net.NetworkStats.IFACE_ALL;
  import static android.net.NetworkStats.INTERFACES_ALL;
  import static android.net.NetworkStats.METERED_ALL;
  import static android.net.NetworkStats.ROAMING_ALL;
  import static android.net.NetworkStats.SET_ALL;
  import static android.net.NetworkStats.SET_DEFAULT;
  import static android.net.NetworkStats.SET_FOREGROUND;
  import static android.net.NetworkStats.STATS_PER_IFACE;
  import static android.net.NetworkStats.STATS_PER_UID;
  import static android.net.NetworkStats.TAG_ALL;
  import static android.net.NetworkStats.TAG_NONE;
  import static android.net.NetworkStats.UID_ALL;
  import static android.net.NetworkStatsHistory.FIELD_ALL;
  import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
  import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
  import static android.net.TrafficStats.KB_IN_BYTES;
  import static android.net.TrafficStats.MB_IN_BYTES;
  import static android.os.Trace.TRACE_TAG_NETWORK;
  import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED;
  import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION;
  import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE;
  import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES;
  import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE;
  import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
  import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
  import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
  import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
  import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
  import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
  import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE;
  import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION;
  import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE;
  import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES;
  import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE;
  import static android.text.format.DateUtils.DAY_IN_MILLIS;
  import static android.text.format.DateUtils.HOUR_IN_MILLIS;
  import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
  import static android.text.format.DateUtils.SECOND_IN_MILLIS;
  
  import static com.android.internal.util.Preconditions.checkNotNull;
  import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
  import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
  import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
  
  import android.annotation.NonNull;
  import android.app.AlarmManager;
  import android.app.PendingIntent;
  import android.app.usage.NetworkStatsManager;
  import android.content.BroadcastReceiver;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageManager;
  import android.net.DataUsageRequest;
  import android.net.INetworkManagementEventObserver;
  import android.net.INetworkStatsService;
  import android.net.INetworkStatsSession;
  import android.net.LinkProperties;
  import android.net.Network;
  import android.net.NetworkCapabilities;
  import android.net.NetworkIdentity;
  import android.net.NetworkInfo;
  import android.net.NetworkState;
  import android.net.NetworkStats;
  import android.net.NetworkStats.NonMonotonicObserver;
  import android.net.NetworkStatsHistory;
  import android.net.NetworkTemplate;
  import android.net.TrafficStats;
  import android.os.BestClock;
  import android.os.Binder;
  import android.os.DropBoxManager;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.IBinder;
  import android.os.INetworkManagementService;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Messenger;
  import android.os.PowerManager;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.Trace;
  import android.os.UserHandle;
  import android.provider.Settings;
  import android.provider.Settings.Global;
  import android.service.NetworkInterfaceProto;
  import android.service.NetworkStatsServiceDumpProto;
  import android.telephony.SubscriptionPlan;
  import android.telephony.TelephonyManager;
  import android.text.format.DateUtils;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.EventLog;
  import android.util.Log;
  import android.util.MathUtils;
  import android.util.Slog;
  import android.util.SparseIntArray;
  import android.util.proto.ProtoOutputStream;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.ArrayUtils;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.FileRotator;
  import com.android.internal.util.IndentingPrintWriter;
  import com.android.server.EventLogTags;
  import com.android.server.LocalServices;
  import com.android.server.connectivity.Tethering;
  
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.time.Clock;
  import java.time.ZoneOffset;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.List;
  
  /**
   * Collect and persist detailed network statistics, and provide this data to
   * other system services.
   */
  public class NetworkStatsService extends INetworkStatsService.Stub {
      static final String TAG = "NetworkStats";
      static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
      static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
  
      // Perform polling and persist all (FLAG_PERSIST_ALL).
      private static final int MSG_PERFORM_POLL = 1;
      // Perform polling, persist network, and register the global alert again.
      private static final int MSG_PERFORM_POLL_REGISTER_ALERT = 2;
  
      /** Flags to control detail level of poll event. */
      private static final int FLAG_PERSIST_NETWORK = 0x1;
      private static final int FLAG_PERSIST_UID = 0x2;
      private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
      private static final int FLAG_PERSIST_FORCE = 0x100;
  
      /**
       * When global alert quota is high, wait for this delay before processing each polling,
       * and do not schedule further polls once there is already one queued.
       * This avoids firing the global alert too often on devices with high transfer speeds and
       * high quota.
       */
      private static final int PERFORM_POLL_DELAY_MS = 1000;
  
      private static final String TAG_NETSTATS_ERROR = "netstats_error";
  
      private final Context mContext;
      private final INetworkManagementService mNetworkManager;
      private final AlarmManager mAlarmManager;
      private final Clock mClock;
      private final TelephonyManager mTeleManager;
      private final NetworkStatsSettings mSettings;
      private final NetworkStatsObservers mStatsObservers;
  
      private final File mSystemDir;
      private final File mBaseDir;
  
      private final PowerManager.WakeLock mWakeLock;
  
      private final boolean mUseBpfTrafficStats;
  
      @VisibleForTesting
      public static final String ACTION_NETWORK_STATS_POLL =
              "com.android.server.action.NETWORK_STATS_POLL";
      public static final String ACTION_NETWORK_STATS_UPDATED =
              "com.android.server.action.NETWORK_STATS_UPDATED";
  
      private PendingIntent mPollIntent;
  
      private static final String PREFIX_DEV = "dev";
      private static final String PREFIX_XT = "xt";
      private static final String PREFIX_UID = "uid";
      private static final String PREFIX_UID_TAG = "uid_tag";
  
      /**
       * Virtual network interface for video telephony. This is for VT data usage counting purpose.
       */
      public static final String VT_INTERFACE = "vt_data0";
  
      /**
       * Settings that can be changed externally.
       */
      public interface NetworkStatsSettings {
          public long getPollInterval();
          public boolean getSampleEnabled();
          public boolean getAugmentEnabled();
  
          public static class Config {
              public final long bucketDuration;
              public final long rotateAgeMillis;
              public final long deleteAgeMillis;
  
              public Config(long bucketDuration, long rotateAgeMillis, long deleteAgeMillis) {
                  this.bucketDuration = bucketDuration;
                  this.rotateAgeMillis = rotateAgeMillis;
                  this.deleteAgeMillis = deleteAgeMillis;
              }
          }
  
          public Config getDevConfig();
          public Config getXtConfig();
          public Config getUidConfig();
          public Config getUidTagConfig();
  
          public long getGlobalAlertBytes(long def);
          public long getDevPersistBytes(long def);
          public long getXtPersistBytes(long def);
          public long getUidPersistBytes(long def);
          public long getUidTagPersistBytes(long def);
      }
  
      private final Object mStatsLock = new Object();
  
      /** Set of currently active ifaces. */
      @GuardedBy("mStatsLock")
      private final ArrayMap<String, NetworkIdentitySet> mActiveIfaces = new ArrayMap<>();
  
      /** Set of currently active ifaces for UID stats. */
      @GuardedBy("mStatsLock")
      private final ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces = new ArrayMap<>();
  
      /** Current default active iface. */
      @GuardedBy("mStatsLock")
      private String mActiveIface;
  
      /** Set of any ifaces associated with mobile networks since boot. */
      @GuardedBy("mStatsLock")
      private String[] mMobileIfaces = new String[0];
  
      /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
      @GuardedBy("mStatsLock")
      private Network[] mDefaultNetworks = new Network[0];
  
      private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
              new DropBoxNonMonotonicObserver();
  
      @GuardedBy("mStatsLock")
      private NetworkStatsRecorder mDevRecorder;
      @GuardedBy("mStatsLock")
      private NetworkStatsRecorder mXtRecorder;
      @GuardedBy("mStatsLock")
      private NetworkStatsRecorder mUidRecorder;
      @GuardedBy("mStatsLock")
      private NetworkStatsRecorder mUidTagRecorder;
  
      /** Cached {@link #mXtRecorder} stats. */
      @GuardedBy("mStatsLock")
      private NetworkStatsCollection mXtStatsCached;
  
      /** Current counter sets for each UID. */
      private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
  
      /** Data layer operation counters for splicing into other structures. */
      private NetworkStats mUidOperations = new NetworkStats(0L, 10);
  
      /** Must be set in factory by calling #setHandler. */
      private Handler mHandler;
      private Handler.Callback mHandlerCallback;
  
      private volatile boolean mSystemReady;
      private long mPersistThreshold = 2 * MB_IN_BYTES;
      private long mGlobalAlertBytes;
  
      private static final long POLL_RATE_LIMIT_MS = 15_000;
  
      private long mLastStatsSessionPoll;
  
      /** Map from UID to number of opened sessions */
      @GuardedBy("mOpenSessionCallsPerUid")
      private final SparseIntArray mOpenSessionCallsPerUid = new SparseIntArray();
  
      private final static int DUMP_STATS_SESSION_COUNT = 20;
  
      private static @NonNull File getDefaultSystemDir() {
          return new File(Environment.getDataDirectory(), "system");
      }
  
      private static @NonNull File getDefaultBaseDir() {
          File baseDir = new File(getDefaultSystemDir(), "netstats");
          baseDir.mkdirs();
          return baseDir;
      }
  
      private static @NonNull Clock getDefaultClock() {
          return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
                  Clock.systemUTC());
      }
  
      private static final class NetworkStatsHandler extends Handler {
          NetworkStatsHandler(Looper looper, Handler.Callback callback) {
              super(looper, callback);
          }
      }
  
      public static NetworkStatsService create(Context context,
                  INetworkManagementService networkManager) {
          AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
          PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
          PowerManager.WakeLock wakeLock =
                  powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
  
          NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
                  wakeLock, getDefaultClock(), TelephonyManager.getDefault(),
                  new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
                  getDefaultSystemDir(), getDefaultBaseDir());
          service.registerLocalService();
  
          HandlerThread handlerThread = new HandlerThread(TAG);
          Handler.Callback callback = new HandlerCallback(service);
          handlerThread.start();
          Handler handler = new NetworkStatsHandler(handlerThread.getLooper(), callback);
          service.setHandler(handler, callback);
          return service;
      }
  
      // This must not be called outside of tests, even within the same package, as this constructor
      // does not register the local service. Use the create() helper above.
      @VisibleForTesting
      NetworkStatsService(Context context, INetworkManagementService networkManager,
              AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock,
              TelephonyManager teleManager, NetworkStatsSettings settings,
              NetworkStatsObservers statsObservers, File systemDir, File baseDir) {
          mContext = checkNotNull(context, "missing Context");
          mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
          mAlarmManager = checkNotNull(alarmManager, "missing AlarmManager");
          mClock = checkNotNull(clock, "missing Clock");
          mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
          mTeleManager = checkNotNull(teleManager, "missing TelephonyManager");
          mWakeLock = checkNotNull(wakeLock, "missing WakeLock");
          mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
          mSystemDir = checkNotNull(systemDir, "missing systemDir");
          mBaseDir = checkNotNull(baseDir, "missing baseDir");
          mUseBpfTrafficStats = new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists();
      }
  
      private void registerLocalService() {
          LocalServices.addService(NetworkStatsManagerInternal.class,
                  new NetworkStatsManagerInternalImpl());
      }
  
      @VisibleForTesting
      void setHandler(Handler handler, Handler.Callback callback) {
          mHandler = handler;
          mHandlerCallback = callback;
      }
  
      public void systemReady() {
          mSystemReady = true;
  
          if (!isBandwidthControlEnabled()) {
              Slog.w(TAG, "bandwidth controls disabled, unable to track stats");
              return;
          }
  
          synchronized (mStatsLock) {
              // create data recorders along with historical rotators
              mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
              mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
              mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
              mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
  
              updatePersistThresholdsLocked();
  
              // upgrade any legacy stats, migrating them to rotated files
              maybeUpgradeLegacyStatsLocked();
  
              // read historical network stats from disk, since policy service
              // might need them right away.
              mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked();
  
              // bootstrap initial stats to prevent double-counting later
              bootstrapStatsLocked();
          }
  
          // watch for tethering changes
          final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
          mContext.registerReceiver(mTetherReceiver, tetherFilter, null, mHandler);
  
          // listen for periodic polling events
          final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
          mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
  
          // listen for uid removal to clean stats
          final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
          mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
  
          // listen for user changes to clean stats
          final IntentFilter userFilter = new IntentFilter(ACTION_USER_REMOVED);
          mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
  
          // persist stats during clean shutdown
          final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
          mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
  
          try {
              mNetworkManager.registerObserver(mAlertObserver);
          } catch (RemoteException e) {
              // ignored; service lives in system_server
          }
  
          registerPollAlarmLocked();
          registerGlobalAlert();
      }
  
      private NetworkStatsRecorder buildRecorder(
              String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
          final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
                  Context.DROPBOX_SERVICE);
          return new NetworkStatsRecorder(new FileRotator(
                  mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
                  mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
      }
  
      @GuardedBy("mStatsLock")
      private void shutdownLocked() {
          mContext.unregisterReceiver(mTetherReceiver);
          mContext.unregisterReceiver(mPollReceiver);
          mContext.unregisterReceiver(mRemovedReceiver);
          mContext.unregisterReceiver(mUserReceiver);
          mContext.unregisterReceiver(mShutdownReceiver);
  
          final long currentTime = mClock.millis();
  
          // persist any pending stats
          mDevRecorder.forcePersistLocked(currentTime);
          mXtRecorder.forcePersistLocked(currentTime);
          mUidRecorder.forcePersistLocked(currentTime);
          mUidTagRecorder.forcePersistLocked(currentTime);
  
          mSystemReady = false;
      }
  
      @GuardedBy("mStatsLock")
      private void maybeUpgradeLegacyStatsLocked() {
          File file;
          try {
              file = new File(mSystemDir, "netstats.bin");
              if (file.exists()) {
                  mDevRecorder.importLegacyNetworkLocked(file);
                  file.delete();
              }
  
              file = new File(mSystemDir, "netstats_xt.bin");
              if (file.exists()) {
                  file.delete();
              }
  
              file = new File(mSystemDir, "netstats_uid.bin");
              if (file.exists()) {
                  mUidRecorder.importLegacyUidLocked(file);
                  mUidTagRecorder.importLegacyUidLocked(file);
                  file.delete();
              }
          } catch (IOException e) {
              Log.wtf(TAG, "problem during legacy upgrade", e);
          } catch (OutOfMemoryError e) {
              Log.wtf(TAG, "problem during legacy upgrade", e);
          }
      }
  
      /**
       * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
       * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
       */
      private void registerPollAlarmLocked() {
          if (mPollIntent != null) {
              mAlarmManager.cancel(mPollIntent);
          }
  
          mPollIntent = PendingIntent.getBroadcast(
                  mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
  
          final long currentRealtime = SystemClock.elapsedRealtime();
          mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
                  mSettings.getPollInterval(), mPollIntent);
      }
  
      /**
       * Register for a global alert that is delivered through
       * {@link INetworkManagementEventObserver} once a threshold amount of data
       * has been transferred.
       */
      private void registerGlobalAlert() {
          try {
              mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
          } catch (IllegalStateException e) {
              Slog.w(TAG, "problem registering for global alert: " + e);
          } catch (RemoteException e) {
              // ignored; service lives in system_server
          }
      }
  
      @Override
      public INetworkStatsSession openSession() {
          // NOTE: if callers want to get non-augmented data, they should go
          // through the public API
          return openSessionInternal(NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, null);
      }
  
      @Override
      public INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage) {
          return openSessionInternal(flags, callingPackage);
      }
  
      private boolean isRateLimitedForPoll(int callingUid) {
          if (callingUid == android.os.Process.SYSTEM_UID) {
              return false;
          }
  
          final long lastCallTime;
          final long now = SystemClock.elapsedRealtime();
          synchronized (mOpenSessionCallsPerUid) {
              int calls = mOpenSessionCallsPerUid.get(callingUid, 0);
              mOpenSessionCallsPerUid.put(callingUid, calls + 1);
              lastCallTime = mLastStatsSessionPoll;
              mLastStatsSessionPoll = now;
          }
  
          return now - lastCallTime < POLL_RATE_LIMIT_MS;
      }
  
      private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
          assertBandwidthControlEnabled();
  
          final int callingUid = Binder.getCallingUid();
          final int usedFlags = isRateLimitedForPoll(callingUid)
                  ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
                  : flags;
          if ((usedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN
                  | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
              final long ident = Binder.clearCallingIdentity();
              try {
                  performPoll(FLAG_PERSIST_ALL);
              } finally {
                  Binder.restoreCallingIdentity(ident);
              }
          }
  
          // return an IBinder which holds strong references to any loaded stats
          // for its lifetime; when caller closes only weak references remain.
  
          return new INetworkStatsSession.Stub() {
              private final int mCallingUid = callingUid;
              private final String mCallingPackage = callingPackage;
              private final @NetworkStatsAccess.Level int mAccessLevel = checkAccessLevel(
                      callingPackage);
  
              private NetworkStatsCollection mUidComplete;
              private NetworkStatsCollection mUidTagComplete;
  
              private NetworkStatsCollection getUidComplete() {
                  synchronized (mStatsLock) {
                      if (mUidComplete == null) {
                          mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
                      }
                      return mUidComplete;
                  }
              }
  
              private NetworkStatsCollection getUidTagComplete() {
                  synchronized (mStatsLock) {
                      if (mUidTagComplete == null) {
                          mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
                      }
                      return mUidTagComplete;
                  }
              }
  
              @Override
              public int[] getRelevantUids() {
                  return getUidComplete().getRelevantUids(mAccessLevel);
              }
  
              @Override
              public NetworkStats getDeviceSummaryForNetwork(
                      NetworkTemplate template, long start, long end) {
                  return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel,
                          mCallingUid);
              }
  
              @Override
              public NetworkStats getSummaryForNetwork(
                      NetworkTemplate template, long start, long end) {
                  return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel,
                          mCallingUid);
              }
  
              @Override
              public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
                  return internalGetHistoryForNetwork(template, usedFlags, fields, mAccessLevel,
                          mCallingUid);
              }
  
              @Override
              public NetworkStats getSummaryForAllUid(
                      NetworkTemplate template, long start, long end, boolean includeTags) {
                  try {
                      final NetworkStats stats = getUidComplete()
                              .getSummary(template, start, end, mAccessLevel, mCallingUid);
                      if (includeTags) {
                          final NetworkStats tagStats = getUidTagComplete()
                                  .getSummary(template, start, end, mAccessLevel, mCallingUid);
                          stats.combineAllValues(tagStats);
                      }
                      return stats;
                  } catch (NullPointerException e) {
                      // TODO: Track down and fix the cause of this crash and remove this catch block.
                      Slog.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
                      throw e;
                  }
              }
  
              @Override
              public NetworkStatsHistory getHistoryForUid(
                      NetworkTemplate template, int uid, int set, int tag, int fields) {
                  // NOTE: We don't augment UID-level statistics
                  if (tag == TAG_NONE) {
                      return getUidComplete().getHistory(template, null, uid, set, tag, fields,
                              Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid);
                  } else {
                      return getUidTagComplete().getHistory(template, null, uid, set, tag, fields,
                              Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid);
                  }
              }
  
              @Override
              public NetworkStatsHistory getHistoryIntervalForUid(
                      NetworkTemplate template, int uid, int set, int tag, int fields,
                      long start, long end) {
                  // NOTE: We don't augment UID-level statistics
                  if (tag == TAG_NONE) {
                      return getUidComplete().getHistory(template, null, uid, set, tag, fields,
                              start, end, mAccessLevel, mCallingUid);
                  } else if (uid == Binder.getCallingUid()) {
                      return getUidTagComplete().getHistory(template, null, uid, set, tag, fields,
                              start, end, mAccessLevel, mCallingUid);
                  } else {
                      throw new SecurityException("Calling package " + mCallingPackage
                              + " cannot access tag information from a different uid");
                  }
              }
  
              @Override
              public void close() {
                  mUidComplete = null;
                  mUidTagComplete = null;
              }
          };
      }
  
      private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
          return NetworkStatsAccess.checkAccessLevel(
                  mContext, Binder.getCallingUid(), callingPackage);
      }
  
      /**
       * Find the most relevant {@link SubscriptionPlan} for the given
       * {@link NetworkTemplate} and flags. This is typically used to augment
       * local measurement results to match a known anchor from the carrier.
       */
      private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) {
          SubscriptionPlan plan = null;
          if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
                  && mSettings.getAugmentEnabled()) {
              if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
              final long token = Binder.clearCallingIdentity();
              try {
                  plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
                          .getSubscriptionPlan(template);
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
              if (LOGD) Slog.d(TAG, "Resolved to plan " + plan);
          }
          return plan;
      }
  
      /**
       * Return network summary, splicing between DEV and XT stats when
       * appropriate.
       */
      private NetworkStats internalGetSummaryForNetwork(NetworkTemplate template, int flags,
              long start, long end, @NetworkStatsAccess.Level int accessLevel, int callingUid) {
          // We've been using pure XT stats long enough that we no longer need to
          // splice DEV and XT together.
          final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
                  accessLevel, callingUid);
  
          final long now = System.currentTimeMillis();
          final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
  
          final NetworkStats stats = new NetworkStats(end - start, 1);
          stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
                  METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets,
                  entry.txBytes, entry.txPackets, entry.operations));
          return stats;
      }
  
      /**
       * Return network history, splicing between DEV and XT stats when
       * appropriate.
       */
      private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template,
              int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) {
          // We've been using pure XT stats long enough that we no longer need to
          // splice DEV and XT together.
          final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags);
          synchronized (mStatsLock) {
              return mXtStatsCached.getHistory(template, augmentPlan,
                      UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE,
                      accessLevel, callingUid);
          }
      }
  
      private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
          assertSystemReady();
          assertBandwidthControlEnabled();
  
          // NOTE: if callers want to get non-augmented data, they should go
          // through the public API
          return internalGetSummaryForNetwork(template,
                  NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, start, end,
                  NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes();
      }
  
      private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
          assertSystemReady();
          assertBandwidthControlEnabled();
  
          final NetworkStatsCollection uidComplete;
          synchronized (mStatsLock) {
              uidComplete = mUidRecorder.getOrLoadCompleteLocked();
          }
          return uidComplete.getSummary(template, start, end, NetworkStatsAccess.Level.DEVICE,
                  android.os.Process.SYSTEM_UID);
      }
  
      @Override
      public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
          if (Binder.getCallingUid() != uid) {
              mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
          }
          assertBandwidthControlEnabled();
  
          // TODO: switch to data layer stats once kernel exports
          // for now, read network layer stats and flatten across all ifaces
          final long token = Binder.clearCallingIdentity();
          final NetworkStats networkLayer;
          try {
              networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid,
                      NetworkStats.INTERFACES_ALL);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
  
          // splice in operation counts
          networkLayer.spliceOperationsFrom(mUidOperations);
  
          final NetworkStats dataLayer = new NetworkStats(
                  networkLayer.getElapsedRealtime(), networkLayer.size());
  
          NetworkStats.Entry entry = null;
          for (int i = 0; i < networkLayer.size(); i++) {
              entry = networkLayer.getValues(i, entry);
              entry.iface = IFACE_ALL;
              dataLayer.combineValues(entry);
          }
  
          return dataLayer;
      }
  
      @Override
      public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
          try {
              final String[] ifacesToQuery =
                      NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
              return getNetworkStatsUidDetail(ifacesToQuery);
          } catch (RemoteException e) {
              Log.wtf(TAG, "Error compiling UID stats", e);
              return new NetworkStats(0L, 0);
          }
      }
  
      @Override
      public String[] getMobileIfaces() {
          return mMobileIfaces;
      }
  
      @Override
      public void incrementOperationCount(int uid, int tag, int operationCount) {
          if (Binder.getCallingUid() != uid) {
              mContext.enforceCallingOrSelfPermission(
                      android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
          }
  
          if (operationCount < 0) {
              throw new IllegalArgumentException("operation count can only be incremented");
          }
          if (tag == TAG_NONE) {
              throw new IllegalArgumentException("operation count must have specific tag");
          }
  
          synchronized (mStatsLock) {
              final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT);
              mUidOperations.combineValues(
                      mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
              mUidOperations.combineValues(
                      mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
          }
      }
  
      @VisibleForTesting
      void setUidForeground(int uid, boolean uidForeground) {
          synchronized (mStatsLock) {
              final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
              final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
              if (oldSet != set) {
                  mActiveUidCounterSet.put(uid, set);
                  setKernelCounterSet(uid, set);
              }
          }
      }
  
      @Override
      public void forceUpdateIfaces(
              Network[] defaultNetworks,
              NetworkState[] networkStates,
              String activeIface) {
          checkNetworkStackPermission(mContext);
          assertBandwidthControlEnabled();
  
          final long token = Binder.clearCallingIdentity();
          try {
              updateIfaces(defaultNetworks, networkStates, activeIface);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      @Override
      public void forceUpdate() {
          mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
          assertBandwidthControlEnabled();
  
          final long token = Binder.clearCallingIdentity();
          try {
              performPoll(FLAG_PERSIST_ALL);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private void advisePersistThreshold(long thresholdBytes) {
          assertBandwidthControlEnabled();
  
          // clamp threshold into safe range
          mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
          if (LOGV) {
              Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
                      + mPersistThreshold);
          }
  
          // update and persist if beyond new thresholds
          final long currentTime = mClock.millis();
          synchronized (mStatsLock) {
              if (!mSystemReady) return;
  
              updatePersistThresholdsLocked();
  
              mDevRecorder.maybePersistLocked(currentTime);
              mXtRecorder.maybePersistLocked(currentTime);
              mUidRecorder.maybePersistLocked(currentTime);
              mUidTagRecorder.maybePersistLocked(currentTime);
          }
  
          // re-arm global alert
          registerGlobalAlert();
      }
  
      @Override
      public DataUsageRequest registerUsageCallback(String callingPackage,
                  DataUsageRequest request, Messenger messenger, IBinder binder) {
          checkNotNull(callingPackage, "calling package is null");
          checkNotNull(request, "DataUsageRequest is null");
          checkNotNull(request.template, "NetworkTemplate is null");
          checkNotNull(messenger, "messenger is null");
          checkNotNull(binder, "binder is null");
  
          int callingUid = Binder.getCallingUid();
          @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
          DataUsageRequest normalizedRequest;
          final long token = Binder.clearCallingIdentity();
          try {
              normalizedRequest = mStatsObservers.register(request, messenger, binder,
                      callingUid, accessLevel);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
  
          // Create baseline stats
          mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL));
  
          return normalizedRequest;
     }
  
      @Override
      public void unregisterUsageRequest(DataUsageRequest request) {
          checkNotNull(request, "DataUsageRequest is null");
  
          int callingUid = Binder.getCallingUid();
          final long token = Binder.clearCallingIdentity();
          try {
              mStatsObservers.unregister(request, callingUid);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      @Override
      public long getUidStats(int uid, int type) {
          return nativeGetUidStat(uid, type, checkBpfStatsEnable());
      }
  
      @Override
      public long getIfaceStats(String iface, int type) {
          long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
          if (nativeIfaceStats == -1) {
              return nativeIfaceStats;
          } else {
              // When tethering offload is in use, nativeIfaceStats does not contain usage from
              // offload, add it back here.
              // When tethering offload is not in use, nativeIfaceStats contains tethering usage.
              // this does not cause double-counting of tethering traffic, because
              // NetdTetheringStatsProvider returns zero NetworkStats
              // when called with STATS_PER_IFACE.
              return nativeIfaceStats + getTetherStats(iface, type);
          }
      }
  
      @Override
      public long getTotalStats(int type) {
          long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable());
          if (nativeTotalStats == -1) {
              return nativeTotalStats;
          } else {
              // Refer to comment in getIfaceStats
              return nativeTotalStats + getTetherStats(IFACE_ALL, type);
          }
      }
  
      private long getTetherStats(String iface, int type) {
          final NetworkStats tetherSnapshot;
          final long token = Binder.clearCallingIdentity();
          try {
              tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
          } catch (RemoteException e) {
              Slog.w(TAG, "Error get TetherStats: " + e);
              return 0;
          } finally {
              Binder.restoreCallingIdentity(token);
          }
          HashSet<String> limitIfaces;
          if (iface == IFACE_ALL) {
              limitIfaces = null;
          } else {
              limitIfaces = new HashSet<String>();
              limitIfaces.add(iface);
          }
          NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces);
          if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type +
                  " entry=" + entry);
          switch (type) {
              case 0: // TYPE_RX_BYTES
                  return entry.rxBytes;
              case 1: // TYPE_RX_PACKETS
                  return entry.rxPackets;
              case 2: // TYPE_TX_BYTES
                  return entry.txBytes;
              case 3: // TYPE_TX_PACKETS
                  return entry.txPackets;
              default:
                  return 0;
          }
      }
  
      private boolean checkBpfStatsEnable() {
          return mUseBpfTrafficStats;
      }
  
      /**
       * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
       * reflect current {@link #mPersistThreshold} value. Always defers to
       * {@link Global} values when defined.
       */
      @GuardedBy("mStatsLock")
      private void updatePersistThresholdsLocked() {
          mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
          mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
          mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
          mUidTagRecorder.setPersistThreshold(mSettings.getUidTagPersistBytes(mPersistThreshold));
          mGlobalAlertBytes = mSettings.getGlobalAlertBytes(mPersistThreshold);
      }
  
      /**
       * Receiver that watches for {@link Tethering} to claim interface pairs.
       */
      private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              // on background handler thread, and verified CONNECTIVITY_INTERNAL
              // permission above.
              performPoll(FLAG_PERSIST_NETWORK);
          }
      };
  
      private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              // on background handler thread, and verified UPDATE_DEVICE_STATS
              // permission above.
              performPoll(FLAG_PERSIST_ALL);
  
              // verify that we're watching global alert
              registerGlobalAlert();
          }
      };
  
      private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              // on background handler thread, and UID_REMOVED is protected
              // broadcast.
  
              final int uid = intent.getIntExtra(EXTRA_UID, -1);
              if (uid == -1) return;
  
              synchronized (mStatsLock) {
                  mWakeLock.acquire();
                  try {
                      removeUidsLocked(uid);
                  } finally {
                      mWakeLock.release();
                  }
              }
          }
      };
  
      private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              // On background handler thread, and USER_REMOVED is protected
              // broadcast.
  
              final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
              if (userId == -1) return;
  
              synchronized (mStatsLock) {
                  mWakeLock.acquire();
                  try {
                      removeUserLocked(userId);
                  } finally {
                      mWakeLock.release();
                  }
              }
          }
      };
  
      private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              // SHUTDOWN is protected broadcast.
              synchronized (mStatsLock) {
                  shutdownLocked();
              }
          }
      };
  
      /**
       * Observer that watches for {@link INetworkManagementService} alerts.
       */
      private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
          @Override
          public void limitReached(String limitName, String iface) {
              // only someone like NMS should be calling us
              mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
  
              if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
                  // kick off background poll to collect network stats unless there is already
                  // such a call pending; UID stats are handled during normal polling interval.
                  if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
                      mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
                              PERFORM_POLL_DELAY_MS);
                  }
              }
          }
      };
  
      private void updateIfaces(
              Network[] defaultNetworks,
              NetworkState[] networkStates,
              String activeIface) {
          synchronized (mStatsLock) {
              mWakeLock.acquire();
              try {
                  mActiveIface = activeIface;
                  updateIfacesLocked(defaultNetworks, networkStates);
              } finally {
                  mWakeLock.release();
              }
          }
      }
  
      /**
       * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link
       * NetworkStatsHistory}. When multiple {@link NetworkInfo} are active on a single {@code iface},
       * they are combined under a single {@link NetworkIdentitySet}.
       */
      @GuardedBy("mStatsLock")
      private void updateIfacesLocked(Network[] defaultNetworks, NetworkState[] states) {
          if (!mSystemReady) return;
          if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
  
          // take one last stats snapshot before updating iface mapping. this
          // isn't perfect, since the kernel may already be counting traffic from
          // the updated network.
  
          // poll, but only persist network stats to keep codepath fast. UID stats
          // will be persisted during next alarm poll event.
          performPollLocked(FLAG_PERSIST_NETWORK);
  
          // Rebuild active interfaces based on connected networks
          mActiveIfaces.clear();
          mActiveUidIfaces.clear();
          if (defaultNetworks != null) {
              // Caller is ConnectivityService. Update the list of default networks.
              mDefaultNetworks = defaultNetworks;
          }
  
          final ArraySet<String> mobileIfaces = new ArraySet<>();
          for (NetworkState state : states) {
              if (state.networkInfo.isConnected()) {
                  final boolean isMobile = isNetworkTypeMobile(state.networkInfo.getType());
                  final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, state.network);
                  final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state,
                          isDefault);
  
                  // Traffic occurring on the base interface is always counted for
                  // both total usage and UID details.
                  final String baseIface = state.linkProperties.getInterfaceName();
                  if (baseIface != null) {
                      findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident);
                      findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident);
  
                      // Build a separate virtual interface for VT (Video Telephony) data usage.
                      // Only do this when IMS is not metered, but VT is metered.
                      // If IMS is metered, then the IMS network usage has already included VT usage.
                      // VT is considered always metered in framework's layer. If VT is not metered
                      // per carrier's policy, modem will report 0 usage for VT calls.
                      if (state.networkCapabilities.hasCapability(
                              NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) {
  
                          // Copy the identify from IMS one but mark it as metered.
                          NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(),
                                  ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
                                  ident.getRoaming(), true /* metered */,
                                  true /* onDefaultNetwork */);
                          findOrCreateNetworkIdentitySet(mActiveIfaces, VT_INTERFACE).add(vtIdent);
                          findOrCreateNetworkIdentitySet(mActiveUidIfaces, VT_INTERFACE).add(vtIdent);
                      }
  
                      if (isMobile) {
                          mobileIfaces.add(baseIface);
                      }
                  }
  
                  // Traffic occurring on stacked interfaces is usually clatd.
                  // UID stats are always counted on the stacked interface and never
                  // on the base interface, because the packets on the base interface
                  // do not actually match application sockets until they are translated.
                  //
                  // Interface stats are more complicated. Packets subject to BPF offload
                  // never appear on the base interface and only appear on the stacked
                  // interface, so to ensure those packets increment interface stats, interface
                  // stats from stacked interfaces must be collected.
                  final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks();
                  for (LinkProperties stackedLink : stackedLinks) {
                      final String stackedIface = stackedLink.getInterfaceName();
                      if (stackedIface != null) {
                          if (mUseBpfTrafficStats) {
                              findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident);
                          }
                          findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident);
                          if (isMobile) {
                              mobileIfaces.add(stackedIface);
                          }
  
                          NetworkStatsFactory.noteStackedIface(stackedIface, baseIface);
                      }
                  }
              }
          }
  
          mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]);
      }
  
      private static <K> NetworkIdentitySet findOrCreateNetworkIdentitySet(
              ArrayMap<K, NetworkIdentitySet> map, K key) {
          NetworkIdentitySet ident = map.get(key);
          if (ident == null) {
              ident = new NetworkIdentitySet();
              map.put(key, ident);
          }
          return ident;
      }
  
      @GuardedBy("mStatsLock")
      private void recordSnapshotLocked(long currentTime) throws RemoteException {
          // snapshot and record current counters; read UID stats first to
          // avoid over counting dev stats.
          Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotUid");
          final NetworkStats uidSnapshot = getNetworkStatsUidDetail(INTERFACES_ALL);
          Trace.traceEnd(TRACE_TAG_NETWORK);
          Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotXt");
          final NetworkStats xtSnapshot = getNetworkStatsXt();
          Trace.traceEnd(TRACE_TAG_NETWORK);
          Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
          final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
          Trace.traceEnd(TRACE_TAG_NETWORK);
  
          // Tethering snapshot for dev and xt stats. Counts per-interface data from tethering stats
          // providers that isn't already counted by dev and XT stats.
          Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotTether");
          final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
          Trace.traceEnd(TRACE_TAG_NETWORK);
          xtSnapshot.combineAllValues(tetherSnapshot);
          devSnapshot.combineAllValues(tetherSnapshot);
  
          // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
          // can't be reattributed to responsible apps.
          Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
          mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
          Trace.traceEnd(TRACE_TAG_NETWORK);
          Trace.traceBegin(TRACE_TAG_NETWORK, "recordXt");
          mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
          Trace.traceEnd(TRACE_TAG_NETWORK);
  
          // For per-UID stats, pass the VPN info so VPN traffic is reattributed to responsible apps.
          Trace.traceBegin(TRACE_TAG_NETWORK, "recordUid");
          mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
          Trace.traceEnd(TRACE_TAG_NETWORK);
          Trace.traceBegin(TRACE_TAG_NETWORK, "recordUidTag");
          mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
          Trace.traceEnd(TRACE_TAG_NETWORK);
  
          // We need to make copies of member fields that are sent to the observer to avoid
          // a race condition between the service handler thread and the observer's
          mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
                  new ArrayMap<>(mActiveUidIfaces), currentTime);
      }
  
      /**
       * Bootstrap initial stats snapshot, usually during {@link #systemReady()}
       * so we have baseline values without double-counting.
       */
      @GuardedBy("mStatsLock")
      private void bootstrapStatsLocked() {
          final long currentTime = mClock.millis();
  
          try {
              recordSnapshotLocked(currentTime);
          } catch (IllegalStateException e) {
              Slog.w(TAG, "problem reading network stats: " + e);
          } catch (RemoteException e) {
              // ignored; service lives in system_server
          }
      }
  
      private void performPoll(int flags) {
          synchronized (mStatsLock) {
              mWakeLock.acquire();
  
              try {
                  performPollLocked(flags);
              } finally {
                  mWakeLock.release();
              }
          }
      }
  
      /**
       * Periodic poll operation, reading current statistics and recording into
       * {@link NetworkStatsHistory}.
       */
      @GuardedBy("mStatsLock")
      private void performPollLocked(int flags) {
          if (!mSystemReady) return;
          if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
          Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
  
          final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
          final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
          final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
  
          // TODO: consider marking "untrusted" times in historical stats
          final long currentTime = mClock.millis();
  
          try {
              recordSnapshotLocked(currentTime);
          } catch (IllegalStateException e) {
              Log.wtf(TAG, "problem reading network stats", e);
              return;
          } catch (RemoteException e) {
              // ignored; service lives in system_server
              return;
          }
  
          // persist any pending data depending on requested flags
          Trace.traceBegin(TRACE_TAG_NETWORK, "[persisting]");
          if (persistForce) {
              mDevRecorder.forcePersistLocked(currentTime);
              mXtRecorder.forcePersistLocked(currentTime);
              mUidRecorder.forcePersistLocked(currentTime);
              mUidTagRecorder.forcePersistLocked(currentTime);
          } else {
              if (persistNetwork) {
                  mDevRecorder.maybePersistLocked(currentTime);
                  mXtRecorder.maybePersistLocked(currentTime);
              }
              if (persistUid) {
                  mUidRecorder.maybePersistLocked(currentTime);
                  mUidTagRecorder.maybePersistLocked(currentTime);
              }
          }
          Trace.traceEnd(TRACE_TAG_NETWORK);
  
          if (mSettings.getSampleEnabled()) {
              // sample stats after each full poll
              performSampleLocked();
          }
  
          // finally, dispatch updated event to any listeners
          final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
          updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
          mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
                  READ_NETWORK_USAGE_HISTORY);
  
          Trace.traceEnd(TRACE_TAG_NETWORK);
      }
  
      /**
       * Sample recent statistics summary into {@link EventLog}.
       */
      @GuardedBy("mStatsLock")
      private void performSampleLocked() {
          // TODO: migrate trustedtime fixes to separate binary log events
          final long currentTime = mClock.millis();
  
          NetworkTemplate template;
          NetworkStats.Entry devTotal;
          NetworkStats.Entry xtTotal;
          NetworkStats.Entry uidTotal;
  
          // collect mobile sample
          template = buildTemplateMobileWildcard();
          devTotal = mDevRecorder.getTotalSinceBootLocked(template);
          xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
          uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
  
          EventLogTags.writeNetstatsMobileSample(
                  devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
                  xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
                  uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
                  currentTime);
  
          // collect wifi sample
          template = buildTemplateWifiWildcard();
          devTotal = mDevRecorder.getTotalSinceBootLocked(template);
          xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
          uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
  
          EventLogTags.writeNetstatsWifiSample(
                  devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
                  xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
                  uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
                  currentTime);
      }
  
      /**
       * Clean up {@link #mUidRecorder} after UID is removed.
       */
      @GuardedBy("mStatsLock")
      private void removeUidsLocked(int... uids) {
          if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
  
          // Perform one last poll before removing
          performPollLocked(FLAG_PERSIST_ALL);
  
          mUidRecorder.removeUidsLocked(uids);
          mUidTagRecorder.removeUidsLocked(uids);
  
          // Clear kernel stats associated with UID
          for (int uid : uids) {
              resetKernelUidStats(uid);
          }
      }
  
      /**
       * Clean up {@link #mUidRecorder} after user is removed.
       */
      @GuardedBy("mStatsLock")
      private void removeUserLocked(int userId) {
          if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
  
          // Build list of UIDs that we should clean up
          int[] uids = new int[0];
          final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
                  PackageManager.MATCH_ANY_USER
                  | PackageManager.MATCH_DISABLED_COMPONENTS);
          for (ApplicationInfo app : apps) {
              final int uid = UserHandle.getUid(userId, app.uid);
              uids = ArrayUtils.appendInt(uids, uid);
          }
  
          removeUidsLocked(uids);
      }
  
      private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
          @Override
          public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
              Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes");
              try {
                  return NetworkStatsService.this.getNetworkTotalBytes(template, start, end);
              } finally {
                  Trace.traceEnd(TRACE_TAG_NETWORK);
              }
          }
  
          @Override
          public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
              Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes");
              try {
                  return NetworkStatsService.this.getNetworkUidBytes(template, start, end);
              } finally {
                  Trace.traceEnd(TRACE_TAG_NETWORK);
              }
          }
  
          @Override
          public void setUidForeground(int uid, boolean uidForeground) {
              NetworkStatsService.this.setUidForeground(uid, uidForeground);
          }
  
          @Override
          public void advisePersistThreshold(long thresholdBytes) {
              NetworkStatsService.this.advisePersistThreshold(thresholdBytes);
          }
  
          @Override
          public void forceUpdate() {
              NetworkStatsService.this.forceUpdate();
          }
      }
  
      @Override
      protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
          if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
  
          long duration = DateUtils.DAY_IN_MILLIS;
          final HashSet<String> argSet = new HashSet<String>();
          for (String arg : args) {
              argSet.add(arg);
  
              if (arg.startsWith("--duration=")) {
                  try {
                      duration = Long.parseLong(arg.substring(11));
                  } catch (NumberFormatException ignored) {
                  }
              }
          }
  
          // usage: dumpsys netstats --full --uid --tag --poll --checkin
          final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
          final boolean checkin = argSet.contains("--checkin");
          final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
          final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
          final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
  
          final IndentingPrintWriter pw = new IndentingPrintWriter(rawWriter, "  ");
  
          synchronized (mStatsLock) {
              if (args.length > 0 && "--proto".equals(args[0])) {
                  // In this case ignore all other arguments.
                  dumpProtoLocked(fd);
                  return;
              }
  
              if (poll) {
                  performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
                  pw.println("Forced poll");
                  return;
              }
  
              if (checkin) {
                  final long end = System.currentTimeMillis();
                  final long start = end - duration;
  
                  pw.print("v1,");
                  pw.print(start / SECOND_IN_MILLIS); pw.print(',');
                  pw.print(end / SECOND_IN_MILLIS); pw.println();
  
                  pw.println("xt");
                  mXtRecorder.dumpCheckin(rawWriter, start, end);
  
                  if (includeUid) {
                      pw.println("uid");
                      mUidRecorder.dumpCheckin(rawWriter, start, end);
                  }
                  if (includeTag) {
                      pw.println("tag");
                      mUidTagRecorder.dumpCheckin(rawWriter, start, end);
                  }
                  return;
              }
  
              pw.println("Active interfaces:");
              pw.increaseIndent();
              for (int i = 0; i < mActiveIfaces.size(); i++) {
                  pw.printPair("iface", mActiveIfaces.keyAt(i));
                  pw.printPair("ident", mActiveIfaces.valueAt(i));
                  pw.println();
              }
              pw.decreaseIndent();
  
              pw.println("Active UID interfaces:");
              pw.increaseIndent();
              for (int i = 0; i < mActiveUidIfaces.size(); i++) {
                  pw.printPair("iface", mActiveUidIfaces.keyAt(i));
                  pw.printPair("ident", mActiveUidIfaces.valueAt(i));
                  pw.println();
              }
              pw.decreaseIndent();
  
              // Get the top openSession callers
              final SparseIntArray calls;
              synchronized (mOpenSessionCallsPerUid) {
                  calls = mOpenSessionCallsPerUid.clone();
              }
  
              final int N = calls.size();
              final long[] values = new long[N];
              for (int j = 0; j < N; j++) {
                  values[j] = ((long) calls.valueAt(j) << 32) | calls.keyAt(j);
              }
              Arrays.sort(values);
  
              pw.println("Top openSession callers (uid=count):");
              pw.increaseIndent();
              final int end = Math.max(0, N - DUMP_STATS_SESSION_COUNT);
              for (int j = N - 1; j >= end; j--) {
                  final int uid = (int) (values[j] & 0xffffffff);
                  final int count = (int) (values[j] >> 32);
                  pw.print(uid); pw.print("="); pw.println(count);
              }
              pw.decreaseIndent();
              pw.println();
  
              pw.println("Dev stats:");
              pw.increaseIndent();
              mDevRecorder.dumpLocked(pw, fullHistory);
              pw.decreaseIndent();
  
              pw.println("Xt stats:");
              pw.increaseIndent();
              mXtRecorder.dumpLocked(pw, fullHistory);
              pw.decreaseIndent();
  
              if (includeUid) {
                  pw.println("UID stats:");
                  pw.increaseIndent();
                  mUidRecorder.dumpLocked(pw, fullHistory);
                  pw.decreaseIndent();
              }
  
              if (includeTag) {
                  pw.println("UID tag stats:");
                  pw.increaseIndent();
                  mUidTagRecorder.dumpLocked(pw, fullHistory);
                  pw.decreaseIndent();
              }
          }
      }
  
      @GuardedBy("mStatsLock")
      private void dumpProtoLocked(FileDescriptor fd) {
          final ProtoOutputStream proto = new ProtoOutputStream(fd);
  
          // TODO Right now it writes all history.  Should it limit to the "since-boot" log?
  
          dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_INTERFACES, mActiveIfaces);
          dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_UID_INTERFACES, mActiveUidIfaces);
          mDevRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.DEV_STATS);
          mXtRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.XT_STATS);
          mUidRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.UID_STATS);
          mUidTagRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.UID_TAG_STATS);
  
          proto.flush();
      }
  
      private static void dumpInterfaces(ProtoOutputStream proto, long tag,
              ArrayMap<String, NetworkIdentitySet> ifaces) {
          for (int i = 0; i < ifaces.size(); i++) {
              final long start = proto.start(tag);
  
              proto.write(NetworkInterfaceProto.INTERFACE, ifaces.keyAt(i));
              ifaces.valueAt(i).writeToProto(proto, NetworkInterfaceProto.IDENTITIES);
  
              proto.end(start);
          }
      }
  
      /**
       * Return snapshot of current UID statistics, including any
       * {@link TrafficStats#UID_TETHERING}, video calling data usage, and {@link #mUidOperations}
       * values.
       *
       * @param ifaces A list of interfaces the stats should be restricted to, or
       *               {@link NetworkStats#INTERFACES_ALL}.
       */
      private NetworkStats getNetworkStatsUidDetail(String[] ifaces)
              throws RemoteException {
          final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL,
                  ifaces);
  
          // fold tethering stats and operations into uid snapshot
          final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
          tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
          NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
                  mUseBpfTrafficStats);
          uidSnapshot.combineAllValues(tetherSnapshot);
  
          final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                  Context.TELEPHONY_SERVICE);
  
          // fold video calling data usage stats into uid snapshot
          final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
          if (vtStats != null) {
              vtStats.filter(UID_ALL, ifaces, TAG_ALL);
              NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
                      mUseBpfTrafficStats);
              uidSnapshot.combineAllValues(vtStats);
          }
  
          uidSnapshot.combineAllValues(mUidOperations);
  
          return uidSnapshot;
      }
  
      /**
       * Return snapshot of current XT statistics with video calling data usage statistics.
       */
      private NetworkStats getNetworkStatsXt() throws RemoteException {
          final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
  
          final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                  Context.TELEPHONY_SERVICE);
  
          // Merge video calling data usage into XT
          final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(STATS_PER_IFACE);
          if (vtSnapshot != null) {
              xtSnapshot.combineAllValues(vtSnapshot);
          }
  
          return xtSnapshot;
      }
  
      /**
       * Return snapshot of current tethering statistics. Will return empty
       * {@link NetworkStats} if any problems are encountered.
       */
      private NetworkStats getNetworkStatsTethering(int how) throws RemoteException {
          try {
              return mNetworkManager.getNetworkStatsTethering(how);
          } catch (IllegalStateException e) {
              Log.wtf(TAG, "problem reading network stats", e);
              return new NetworkStats(0L, 10);
          }
      }
  
      @VisibleForTesting
      static class HandlerCallback implements Handler.Callback {
          private final NetworkStatsService mService;
  
          HandlerCallback(NetworkStatsService service) {
              this.mService = service;
          }
  
          @Override
          public boolean handleMessage(Message msg) {
              switch (msg.what) {
                  case MSG_PERFORM_POLL: {
                      mService.performPoll(FLAG_PERSIST_ALL);
                      return true;
                  }
                  case MSG_PERFORM_POLL_REGISTER_ALERT: {
                      mService.performPoll(FLAG_PERSIST_NETWORK);
                      mService.registerGlobalAlert();
                      return true;
                  }
                  default: {
                      return false;
                  }
              }
          }
      }
  
      private void assertSystemReady() {
          if (!mSystemReady) {
              throw new IllegalStateException("System not ready");
          }
      }
  
      private void assertBandwidthControlEnabled() {
          if (!isBandwidthControlEnabled()) {
              throw new IllegalStateException("Bandwidth module disabled");
          }
      }
  
      private boolean isBandwidthControlEnabled() {
          final long token = Binder.clearCallingIdentity();
          try {
              return mNetworkManager.isBandwidthControlEnabled();
          } catch (RemoteException e) {
              // ignored; service lives in system_server
              return false;
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
          @Override
          public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
                  int rightIndex, String cookie) {
              Log.w(TAG, "Found non-monotonic values; saving to dropbox");
  
              // record error for debugging
              final StringBuilder builder = new StringBuilder();
              builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex
                      + "] - right[" + rightIndex + "]\n");
              builder.append("left=").append(left).append('\n');
              builder.append("right=").append(right).append('\n');
  
              mContext.getSystemService(DropBoxManager.class).addText(TAG_NETSTATS_ERROR,
                      builder.toString());
          }
  
          @Override
          public void foundNonMonotonic(
                  NetworkStats stats, int statsIndex, String cookie) {
              Log.w(TAG, "Found non-monotonic values; saving to dropbox");
  
              final StringBuilder builder = new StringBuilder();
              builder.append("Found non-monotonic " + cookie + " values at [" + statsIndex + "]\n");
              builder.append("stats=").append(stats).append('\n');
  
              mContext.getSystemService(DropBoxManager.class).addText(TAG_NETSTATS_ERROR,
                      builder.toString());
          }
      }
  
      /**
       * Default external settings that read from
       * {@link android.provider.Settings.Global}.
       */
      private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
          private final ContentResolver mResolver;
  
          public DefaultNetworkStatsSettings(Context context) {
              mResolver = checkNotNull(context.getContentResolver());
              // TODO: adjust these timings for production builds
          }
  
          private long getGlobalLong(String name, long def) {
              return Settings.Global.getLong(mResolver, name, def);
          }
          private boolean getGlobalBoolean(String name, boolean def) {
              final int defInt = def ? 1 : 0;
              return Settings.Global.getInt(mResolver, name, defInt) != 0;
          }
  
          @Override
          public long getPollInterval() {
              return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
          }
          @Override
          public long getGlobalAlertBytes(long def) {
              return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
          }
          @Override
          public boolean getSampleEnabled() {
              return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true);
          }
          @Override
          public boolean getAugmentEnabled() {
              return getGlobalBoolean(NETSTATS_AUGMENT_ENABLED, true);
          }
          @Override
          public Config getDevConfig() {
              return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
                      getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
                      getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
          }
          @Override
          public Config getXtConfig() {
              return getDevConfig();
          }
          @Override
          public Config getUidConfig() {
              return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
                      getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
                      getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
          }
          @Override
          public Config getUidTagConfig() {
              return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
                      getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
                      getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
          }
          @Override
          public long getDevPersistBytes(long def) {
              return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def);
          }
          @Override
          public long getXtPersistBytes(long def) {
              return getDevPersistBytes(def);
          }
          @Override
          public long getUidPersistBytes(long def) {
              return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def);
          }
          @Override
          public long getUidTagPersistBytes(long def) {
              return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
          }
      }
  
      private static int TYPE_RX_BYTES;
      private static int TYPE_RX_PACKETS;
      private static int TYPE_TX_BYTES;
      private static int TYPE_TX_PACKETS;
      private static int TYPE_TCP_RX_PACKETS;
      private static int TYPE_TCP_TX_PACKETS;
  
      private static native long nativeGetTotalStat(int type, boolean useBpfStats);
      private static native long nativeGetIfaceStat(String iface, int type, boolean useBpfStats);
      private static native long nativeGetUidStat(int uid, int type, boolean useBpfStats);
  }
  