1 /** 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package android.app.usage; 18 19 import android.content.Context; 20 import android.net.INetworkStatsService; 21 import android.net.INetworkStatsSession; 22 import android.net.NetworkStatsHistory; 23 import android.net.NetworkTemplate; 24 import android.net.TrafficStats; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.util.IntArray; 28 import android.util.Log; 29 30 import dalvik.system.CloseGuard; 31 32 /** 33 * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects 34 * are returned as results to various queries in {@link NetworkStatsManager}. 35 */ 36 public final class NetworkStats implements AutoCloseable { 37 private final static String TAG = "NetworkStats"; 38 39 private final CloseGuard mCloseGuard = CloseGuard.get(); 40 41 /** 42 * Start timestamp of stats collected 43 */ 44 private final long mStartTimeStamp; 45 46 /** 47 * End timestamp of stats collected 48 */ 49 private final long mEndTimeStamp; 50 51 52 /** 53 * Non-null array indicates the query enumerates over uids. 54 */ 55 private int[] mUids; 56 57 /** 58 * Index of the current uid in mUids when doing uid enumeration or a single uid value, 59 * depending on query type. 60 */ 61 private int mUidOrUidIndex; 62 63 /** 64 * The session while the query requires it, null if all the stats have been collected or close() 65 * has been called. 66 */ 67 private INetworkStatsSession mSession; 68 private NetworkTemplate mTemplate; 69 70 /** 71 * Results of a summary query. 72 */ 73 private android.net.NetworkStats mSummary = null; 74 75 /** 76 * Results of detail queries. 77 */ 78 private NetworkStatsHistory mHistory = null; 79 80 /** 81 * Where we are in enumerating over the current result. 82 */ 83 private int mEnumerationIndex = 0; 84 85 /** 86 * Recycling entry objects to prevent heap fragmentation. 87 */ 88 private android.net.NetworkStats.Entry mRecycledSummaryEntry = null; 89 private NetworkStatsHistory.Entry mRecycledHistoryEntry = null; 90 91 /** @hide */ NetworkStats(Context context, NetworkTemplate template, long startTimestamp, long endTimestamp)92 NetworkStats(Context context, NetworkTemplate template, long startTimestamp, 93 long endTimestamp) throws RemoteException, SecurityException { 94 final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( 95 ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); 96 // Open network stats session 97 mSession = statsService.openSessionForUsageStats(context.getOpPackageName()); 98 mCloseGuard.open("close"); 99 mTemplate = template; 100 mStartTimeStamp = startTimestamp; 101 mEndTimeStamp = endTimestamp; 102 } 103 104 @Override finalize()105 protected void finalize() throws Throwable { 106 try { 107 if (mCloseGuard != null) { 108 mCloseGuard.warnIfOpen(); 109 } 110 close(); 111 } finally { 112 super.finalize(); 113 } 114 } 115 116 // -------------------------BEGINNING OF PUBLIC API----------------------------------- 117 118 /** 119 * Buckets are the smallest elements of a query result. As some dimensions of a result may be 120 * aggregated (e.g. time or state) some values may be equal across all buckets. 121 */ 122 public static class Bucket { 123 /** 124 * Combined usage across all other states. 125 */ 126 public static final int STATE_ALL = -1; 127 128 /** 129 * Usage not accounted in any other states. 130 */ 131 public static final int STATE_DEFAULT = 0x1; 132 133 /** 134 * Foreground usage. 135 */ 136 public static final int STATE_FOREGROUND = 0x2; 137 138 /** 139 * Special UID value for aggregate/unspecified. 140 */ 141 public static final int UID_ALL = android.net.NetworkStats.UID_ALL; 142 143 /** 144 * Special UID value for removed apps. 145 */ 146 public static final int UID_REMOVED = TrafficStats.UID_REMOVED; 147 148 /** 149 * Special UID value for data usage by tethering. 150 */ 151 public static final int UID_TETHERING = TrafficStats.UID_TETHERING; 152 153 private int mUid; 154 private int mState; 155 private long mBeginTimeStamp; 156 private long mEndTimeStamp; 157 private long mRxBytes; 158 private long mRxPackets; 159 private long mTxBytes; 160 private long mTxPackets; 161 convertState(int networkStatsSet)162 private static int convertState(int networkStatsSet) { 163 switch (networkStatsSet) { 164 case android.net.NetworkStats.SET_ALL : return STATE_ALL; 165 case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT; 166 case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND; 167 } 168 return 0; 169 } 170 convertUid(int uid)171 private static int convertUid(int uid) { 172 switch (uid) { 173 case TrafficStats.UID_REMOVED: return UID_REMOVED; 174 case TrafficStats.UID_TETHERING: return UID_TETHERING; 175 } 176 return uid; 177 } 178 Bucket()179 public Bucket() { 180 } 181 182 /** 183 * Key of the bucket. Usually an app uid or one of the following special values:<p /> 184 * <ul> 185 * <li>{@link #UID_REMOVED}</li> 186 * <li>{@link #UID_TETHERING}</li> 187 * <li>{@link android.os.Process#SYSTEM_UID}</li> 188 * </ul> 189 * @return Bucket key. 190 */ getUid()191 public int getUid() { 192 return mUid; 193 } 194 195 /** 196 * Usage state. One of the following values:<p/> 197 * <ul> 198 * <li>{@link #STATE_ALL}</li> 199 * <li>{@link #STATE_DEFAULT}</li> 200 * <li>{@link #STATE_FOREGROUND}</li> 201 * </ul> 202 * @return Usage state. 203 */ getState()204 public int getState() { 205 return mState; 206 } 207 208 /** 209 * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see 210 * {@link java.lang.System#currentTimeMillis}. 211 * @return Start of interval. 212 */ getStartTimeStamp()213 public long getStartTimeStamp() { 214 return mBeginTimeStamp; 215 } 216 217 /** 218 * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see 219 * {@link java.lang.System#currentTimeMillis}. 220 * @return End of interval. 221 */ getEndTimeStamp()222 public long getEndTimeStamp() { 223 return mEndTimeStamp; 224 } 225 226 /** 227 * Number of bytes received during the bucket's time interval. Statistics are measured at 228 * the network layer, so they include both TCP and UDP usage. 229 * @return Number of bytes. 230 */ getRxBytes()231 public long getRxBytes() { 232 return mRxBytes; 233 } 234 235 /** 236 * Number of bytes transmitted during the bucket's time interval. Statistics are measured at 237 * the network layer, so they include both TCP and UDP usage. 238 * @return Number of bytes. 239 */ getTxBytes()240 public long getTxBytes() { 241 return mTxBytes; 242 } 243 244 /** 245 * Number of packets received during the bucket's time interval. Statistics are measured at 246 * the network layer, so they include both TCP and UDP usage. 247 * @return Number of packets. 248 */ getRxPackets()249 public long getRxPackets() { 250 return mRxPackets; 251 } 252 253 /** 254 * Number of packets transmitted during the bucket's time interval. Statistics are measured 255 * at the network layer, so they include both TCP and UDP usage. 256 * @return Number of packets. 257 */ getTxPackets()258 public long getTxPackets() { 259 return mTxPackets; 260 } 261 } 262 263 /** 264 * Fills the recycled bucket with data of the next bin in the enumeration. 265 * @param bucketOut Bucket to be filled with data. 266 * @return true if successfully filled the bucket, false otherwise. 267 */ getNextBucket(Bucket bucketOut)268 public boolean getNextBucket(Bucket bucketOut) { 269 if (mSummary != null) { 270 return getNextSummaryBucket(bucketOut); 271 } else { 272 return getNextHistoryBucket(bucketOut); 273 } 274 } 275 276 /** 277 * Check if it is possible to ask for a next bucket in the enumeration. 278 * @return true if there is at least one more bucket. 279 */ hasNextBucket()280 public boolean hasNextBucket() { 281 if (mSummary != null) { 282 return mEnumerationIndex < mSummary.size(); 283 } else if (mHistory != null) { 284 return mEnumerationIndex < mHistory.size() 285 || hasNextUid(); 286 } 287 return false; 288 } 289 290 /** 291 * Closes the enumeration. Call this method before this object gets out of scope. 292 */ 293 @Override close()294 public void close() { 295 if (mSession != null) { 296 try { 297 mSession.close(); 298 } catch (RemoteException e) { 299 Log.w(TAG, e); 300 // Otherwise, meh 301 } 302 } 303 mSession = null; 304 if (mCloseGuard != null) { 305 mCloseGuard.close(); 306 } 307 } 308 309 // -------------------------END OF PUBLIC API----------------------------------- 310 311 /** 312 * Collects device summary results into a Bucket. 313 * @throws RemoteException 314 */ getDeviceSummaryForNetwork()315 Bucket getDeviceSummaryForNetwork() throws RemoteException { 316 mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp); 317 318 // Setting enumeration index beyond end to avoid accidental enumeration over data that does 319 // not belong to the calling user. 320 mEnumerationIndex = mSummary.size(); 321 322 return getSummaryAggregate(); 323 } 324 325 /** 326 * Collects summary results and sets summary enumeration mode. 327 * @throws RemoteException 328 */ startSummaryEnumeration()329 void startSummaryEnumeration() throws RemoteException { 330 mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp, false); 331 332 mEnumerationIndex = 0; 333 } 334 335 /** 336 * Collects history results for uid and resets history enumeration index. 337 */ startHistoryEnumeration(int uid)338 void startHistoryEnumeration(int uid) { 339 mHistory = null; 340 try { 341 mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, 342 android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, 343 NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); 344 setSingleUid(uid); 345 } catch (RemoteException e) { 346 Log.w(TAG, e); 347 // Leaving mHistory null 348 } 349 mEnumerationIndex = 0; 350 } 351 352 /** 353 * Starts uid enumeration for current user. 354 * @throws RemoteException 355 */ startUserUidEnumeration()356 void startUserUidEnumeration() throws RemoteException { 357 // TODO: getRelevantUids should be sensitive to time interval. When that's done, 358 // the filtering logic below can be removed. 359 int[] uids = mSession.getRelevantUids(); 360 // Filtering of uids with empty history. 361 IntArray filteredUids = new IntArray(uids.length); 362 for (int uid : uids) { 363 try { 364 NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid, 365 android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, 366 NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); 367 if (history != null && history.size() > 0) { 368 filteredUids.add(uid); 369 } 370 } catch (RemoteException e) { 371 Log.w(TAG, "Error while getting history of uid " + uid, e); 372 } 373 } 374 mUids = filteredUids.toArray(); 375 mUidOrUidIndex = -1; 376 stepHistory(); 377 } 378 379 /** 380 * Steps to next uid in enumeration and collects history for that. 381 */ stepHistory()382 private void stepHistory(){ 383 if (hasNextUid()) { 384 stepUid(); 385 mHistory = null; 386 try { 387 mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(), 388 android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, 389 NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); 390 } catch (RemoteException e) { 391 Log.w(TAG, e); 392 // Leaving mHistory null 393 } 394 mEnumerationIndex = 0; 395 } 396 } 397 fillBucketFromSummaryEntry(Bucket bucketOut)398 private void fillBucketFromSummaryEntry(Bucket bucketOut) { 399 bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); 400 bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); 401 bucketOut.mBeginTimeStamp = mStartTimeStamp; 402 bucketOut.mEndTimeStamp = mEndTimeStamp; 403 bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes; 404 bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets; 405 bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes; 406 bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets; 407 } 408 409 /** 410 * Getting the next item in summary enumeration. 411 * @param bucketOut Next item will be set here. 412 * @return true if a next item could be set. 413 */ getNextSummaryBucket(Bucket bucketOut)414 private boolean getNextSummaryBucket(Bucket bucketOut) { 415 if (bucketOut != null && mEnumerationIndex < mSummary.size()) { 416 mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry); 417 fillBucketFromSummaryEntry(bucketOut); 418 return true; 419 } 420 return false; 421 } 422 getSummaryAggregate()423 Bucket getSummaryAggregate() { 424 if (mSummary == null) { 425 return null; 426 } 427 Bucket bucket = new Bucket(); 428 if (mRecycledSummaryEntry == null) { 429 mRecycledSummaryEntry = new android.net.NetworkStats.Entry(); 430 } 431 mSummary.getTotal(mRecycledSummaryEntry); 432 fillBucketFromSummaryEntry(bucket); 433 return bucket; 434 } 435 /** 436 * Getting the next item in a history enumeration. 437 * @param bucketOut Next item will be set here. 438 * @return true if a next item could be set. 439 */ getNextHistoryBucket(Bucket bucketOut)440 private boolean getNextHistoryBucket(Bucket bucketOut) { 441 if (bucketOut != null && mHistory != null) { 442 if (mEnumerationIndex < mHistory.size()) { 443 mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++, 444 mRecycledHistoryEntry); 445 bucketOut.mUid = Bucket.convertUid(getUid()); 446 bucketOut.mState = Bucket.STATE_ALL; 447 bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; 448 bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart + 449 mRecycledHistoryEntry.bucketDuration; 450 bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes; 451 bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets; 452 bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes; 453 bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets; 454 return true; 455 } else if (hasNextUid()) { 456 stepHistory(); 457 return getNextHistoryBucket(bucketOut); 458 } 459 } 460 return false; 461 } 462 463 // ------------------ UID LOGIC------------------------ 464 isUidEnumeration()465 private boolean isUidEnumeration() { 466 return mUids != null; 467 } 468 hasNextUid()469 private boolean hasNextUid() { 470 return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length; 471 } 472 getUid()473 private int getUid() { 474 // Check if uid enumeration. 475 if (isUidEnumeration()) { 476 if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) { 477 throw new IndexOutOfBoundsException( 478 "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length); 479 } 480 return mUids[mUidOrUidIndex]; 481 } 482 // Single uid mode. 483 return mUidOrUidIndex; 484 } 485 setSingleUid(int uid)486 private void setSingleUid(int uid) { 487 mUidOrUidIndex = uid; 488 } 489 stepUid()490 private void stepUid() { 491 if (mUids != null) { 492 ++mUidOrUidIndex; 493 } 494 } 495 } 496