1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.net;
18 
19 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
20 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
21 import static android.net.NetworkStats.IFACE_ALL;
22 import static android.net.NetworkStats.METERED_NO;
23 import static android.net.NetworkStats.METERED_YES;
24 import static android.net.NetworkStats.ROAMING_NO;
25 import static android.net.NetworkStats.ROAMING_YES;
26 import static android.net.NetworkStats.SET_ALL;
27 import static android.net.NetworkStats.SET_DEFAULT;
28 import static android.net.NetworkStats.TAG_NONE;
29 import static android.net.NetworkStats.UID_ALL;
30 import static android.net.TrafficStats.UID_REMOVED;
31 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
32 
33 import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
34 import static com.android.server.net.NetworkStatsService.TAG;
35 
36 import android.net.NetworkIdentity;
37 import android.net.NetworkStats;
38 import android.net.NetworkStatsHistory;
39 import android.net.NetworkTemplate;
40 import android.net.TrafficStats;
41 import android.os.Binder;
42 import android.service.NetworkStatsCollectionKeyProto;
43 import android.service.NetworkStatsCollectionProto;
44 import android.service.NetworkStatsCollectionStatsProto;
45 import android.telephony.SubscriptionPlan;
46 import android.text.format.DateUtils;
47 import android.util.ArrayMap;
48 import android.util.AtomicFile;
49 import android.util.IntArray;
50 import android.util.MathUtils;
51 import android.util.Range;
52 import android.util.Slog;
53 import android.util.proto.ProtoOutputStream;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.util.ArrayUtils;
57 import com.android.internal.util.FastDataInput;
58 import com.android.internal.util.FastDataOutput;
59 import com.android.internal.util.FileRotator;
60 import com.android.internal.util.IndentingPrintWriter;
61 
62 import libcore.io.IoUtils;
63 
64 import com.google.android.collect.Lists;
65 import com.google.android.collect.Maps;
66 
67 import java.io.BufferedInputStream;
68 import java.io.DataInput;
69 import java.io.DataInputStream;
70 import java.io.DataOutput;
71 import java.io.DataOutputStream;
72 import java.io.File;
73 import java.io.FileNotFoundException;
74 import java.io.IOException;
75 import java.io.InputStream;
76 import java.io.OutputStream;
77 import java.io.PrintWriter;
78 import java.net.ProtocolException;
79 import java.time.ZonedDateTime;
80 import java.util.ArrayList;
81 import java.util.Collections;
82 import java.util.HashMap;
83 import java.util.Iterator;
84 import java.util.Objects;
85 
86 /**
87  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
88  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
89  */
90 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
91     /** File header magic number: "ANET" */
92     private static final int FILE_MAGIC = 0x414E4554;
93 
94     /** Default buffer size from BufferedInputStream */
95     private static final int BUFFER_SIZE = 8192;
96 
97     private static final int VERSION_NETWORK_INIT = 1;
98 
99     private static final int VERSION_UID_INIT = 1;
100     private static final int VERSION_UID_WITH_IDENT = 2;
101     private static final int VERSION_UID_WITH_TAG = 3;
102     private static final int VERSION_UID_WITH_SET = 4;
103 
104     private static final int VERSION_UNIFIED_INIT = 16;
105 
106     private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
107 
108     private final long mBucketDuration;
109 
110     private long mStartMillis;
111     private long mEndMillis;
112     private long mTotalBytes;
113     private boolean mDirty;
114 
NetworkStatsCollection(long bucketDuration)115     public NetworkStatsCollection(long bucketDuration) {
116         mBucketDuration = bucketDuration;
117         reset();
118     }
119 
clear()120     public void clear() {
121         reset();
122     }
123 
reset()124     public void reset() {
125         mStats.clear();
126         mStartMillis = Long.MAX_VALUE;
127         mEndMillis = Long.MIN_VALUE;
128         mTotalBytes = 0;
129         mDirty = false;
130     }
131 
getStartMillis()132     public long getStartMillis() {
133         return mStartMillis;
134     }
135 
136     /**
137      * Return first atomic bucket in this collection, which is more conservative
138      * than {@link #mStartMillis}.
139      */
getFirstAtomicBucketMillis()140     public long getFirstAtomicBucketMillis() {
141         if (mStartMillis == Long.MAX_VALUE) {
142             return Long.MAX_VALUE;
143         } else {
144             return mStartMillis + mBucketDuration;
145         }
146     }
147 
getEndMillis()148     public long getEndMillis() {
149         return mEndMillis;
150     }
151 
getTotalBytes()152     public long getTotalBytes() {
153         return mTotalBytes;
154     }
155 
isDirty()156     public boolean isDirty() {
157         return mDirty;
158     }
159 
clearDirty()160     public void clearDirty() {
161         mDirty = false;
162     }
163 
isEmpty()164     public boolean isEmpty() {
165         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
166     }
167 
168     @VisibleForTesting
roundUp(long time)169     public long roundUp(long time) {
170         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
171                 || time == SubscriptionPlan.TIME_UNKNOWN) {
172             return time;
173         } else {
174             final long mod = time % mBucketDuration;
175             if (mod > 0) {
176                 time -= mod;
177                 time += mBucketDuration;
178             }
179             return time;
180         }
181     }
182 
183     @VisibleForTesting
roundDown(long time)184     public long roundDown(long time) {
185         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
186                 || time == SubscriptionPlan.TIME_UNKNOWN) {
187             return time;
188         } else {
189             final long mod = time % mBucketDuration;
190             if (mod > 0) {
191                 time -= mod;
192             }
193             return time;
194         }
195     }
196 
getRelevantUids(@etworkStatsAccess.Level int accessLevel)197     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
198         return getRelevantUids(accessLevel, Binder.getCallingUid());
199     }
200 
getRelevantUids(@etworkStatsAccess.Level int accessLevel, final int callerUid)201     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
202                 final int callerUid) {
203         IntArray uids = new IntArray();
204         for (int i = 0; i < mStats.size(); i++) {
205             final Key key = mStats.keyAt(i);
206             if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
207                 int j = uids.binarySearch(key.uid);
208 
209                 if (j < 0) {
210                     j = ~j;
211                     uids.add(j, key.uid);
212                 }
213             }
214         }
215         return uids.toArray();
216     }
217 
218     /**
219      * Combine all {@link NetworkStatsHistory} in this collection which match
220      * the requested parameters.
221      */
getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, int uid, int set, int tag, int fields, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)222     public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
223             int uid, int set, int tag, int fields, long start, long end,
224             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
225         if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
226             throw new SecurityException("Network stats history of uid " + uid
227                     + " is forbidden for caller " + callerUid);
228         }
229 
230         // 180 days of history should be enough for anyone; if we end up needing
231         // more, we'll dynamically grow the history object.
232         final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0,
233                 (180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration);
234         final NetworkStatsHistory combined = new NetworkStatsHistory(
235                 mBucketDuration, bucketEstimate, fields);
236 
237         // shortcut when we know stats will be empty
238         if (start == end) return combined;
239 
240         // Figure out the window of time that we should be augmenting (if any)
241         long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
242         long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
243                 : SubscriptionPlan.TIME_UNKNOWN;
244         // And if augmenting, we might need to collect more data to adjust with
245         long collectStart = start;
246         long collectEnd = end;
247 
248         if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
249             final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator();
250             while (it.hasNext()) {
251                 final Range<ZonedDateTime> cycle = it.next();
252                 final long cycleStart = cycle.getLower().toInstant().toEpochMilli();
253                 final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli();
254                 if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
255                     augmentStart = cycleStart;
256                     collectStart = Long.min(collectStart, augmentStart);
257                     collectEnd = Long.max(collectEnd, augmentEnd);
258                     break;
259                 }
260             }
261         }
262 
263         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
264             // Shrink augmentation window so we don't risk undercounting.
265             augmentStart = roundUp(augmentStart);
266             augmentEnd = roundDown(augmentEnd);
267             // Grow collection window so we get all the stats needed.
268             collectStart = roundDown(collectStart);
269             collectEnd = roundUp(collectEnd);
270         }
271 
272         for (int i = 0; i < mStats.size(); i++) {
273             final Key key = mStats.keyAt(i);
274             if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
275                     && templateMatches(template, key.ident)) {
276                 final NetworkStatsHistory value = mStats.valueAt(i);
277                 combined.recordHistory(value, collectStart, collectEnd);
278             }
279         }
280 
281         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
282             final NetworkStatsHistory.Entry entry = combined.getValues(
283                     augmentStart, augmentEnd, null);
284 
285             // If we don't have any recorded data for this time period, give
286             // ourselves something to scale with.
287             if (entry.rxBytes == 0 || entry.txBytes == 0) {
288                 combined.recordData(augmentStart, augmentEnd,
289                         new NetworkStats.Entry(1, 0, 1, 0, 0));
290                 combined.getValues(augmentStart, augmentEnd, entry);
291             }
292 
293             final long rawBytes = (entry.rxBytes + entry.txBytes) == 0 ? 1 :
294                     (entry.rxBytes + entry.txBytes);
295             final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes;
296             final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes;
297             final long targetBytes = augmentPlan.getDataUsageBytes();
298 
299             final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes);
300             final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes);
301 
302 
303             // Scale all matching buckets to reach anchor target
304             final long beforeTotal = combined.getTotalBytes();
305             for (int i = 0; i < combined.size(); i++) {
306                 combined.getValues(i, entry);
307                 if (entry.bucketStart >= augmentStart
308                         && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
309                     entry.rxBytes = multiplySafeByRational(
310                             targetRxBytes, entry.rxBytes, rawRxBytes);
311                     entry.txBytes = multiplySafeByRational(
312                             targetTxBytes, entry.txBytes, rawTxBytes);
313                     // We purposefully clear out packet counters to indicate
314                     // that this data has been augmented.
315                     entry.rxPackets = 0;
316                     entry.txPackets = 0;
317                     combined.setValues(i, entry);
318                 }
319             }
320 
321             final long deltaTotal = combined.getTotalBytes() - beforeTotal;
322             if (deltaTotal != 0) {
323                 Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
324             }
325 
326             // Finally we can slice data as originally requested
327             final NetworkStatsHistory sliced = new NetworkStatsHistory(
328                     mBucketDuration, bucketEstimate, fields);
329             sliced.recordHistory(combined, start, end);
330             return sliced;
331         } else {
332             return combined;
333         }
334     }
335 
336     /**
337      * Summarize all {@link NetworkStatsHistory} in this collection which match
338      * the requested parameters.
339      */
getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)340     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
341             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
342         final long now = System.currentTimeMillis();
343 
344         final NetworkStats stats = new NetworkStats(end - start, 24);
345 
346         // shortcut when we know stats will be empty
347         if (start == end) return stats;
348 
349         final NetworkStats.Entry entry = new NetworkStats.Entry();
350         NetworkStatsHistory.Entry historyEntry = null;
351 
352         for (int i = 0; i < mStats.size(); i++) {
353             final Key key = mStats.keyAt(i);
354             if (templateMatches(template, key.ident)
355                     && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
356                     && key.set < NetworkStats.SET_DEBUG_START) {
357                 final NetworkStatsHistory value = mStats.valueAt(i);
358                 historyEntry = value.getValues(start, end, now, historyEntry);
359 
360                 entry.iface = IFACE_ALL;
361                 entry.uid = key.uid;
362                 entry.set = key.set;
363                 entry.tag = key.tag;
364                 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ?
365                         DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
366                 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
367                 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
368                 entry.rxBytes = historyEntry.rxBytes;
369                 entry.rxPackets = historyEntry.rxPackets;
370                 entry.txBytes = historyEntry.txBytes;
371                 entry.txPackets = historyEntry.txPackets;
372                 entry.operations = historyEntry.operations;
373 
374                 if (!entry.isEmpty()) {
375                     stats.combineValues(entry);
376                 }
377             }
378         }
379 
380         return stats;
381     }
382 
383     /**
384      * Record given {@link android.net.NetworkStats.Entry} into this collection.
385      */
recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry)386     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
387             long end, NetworkStats.Entry entry) {
388         final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
389         history.recordData(start, end, entry);
390         noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
391     }
392 
393     /**
394      * Record given {@link NetworkStatsHistory} into this collection.
395      */
recordHistory(Key key, NetworkStatsHistory history)396     private void recordHistory(Key key, NetworkStatsHistory history) {
397         if (history.size() == 0) return;
398         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
399 
400         NetworkStatsHistory target = mStats.get(key);
401         if (target == null) {
402             target = new NetworkStatsHistory(history.getBucketDuration());
403             mStats.put(key, target);
404         }
405         target.recordEntireHistory(history);
406     }
407 
408     /**
409      * Record all {@link NetworkStatsHistory} contained in the given collection
410      * into this collection.
411      */
recordCollection(NetworkStatsCollection another)412     public void recordCollection(NetworkStatsCollection another) {
413         for (int i = 0; i < another.mStats.size(); i++) {
414             final Key key = another.mStats.keyAt(i);
415             final NetworkStatsHistory value = another.mStats.valueAt(i);
416             recordHistory(key, value);
417         }
418     }
419 
findOrCreateHistory( NetworkIdentitySet ident, int uid, int set, int tag)420     private NetworkStatsHistory findOrCreateHistory(
421             NetworkIdentitySet ident, int uid, int set, int tag) {
422         final Key key = new Key(ident, uid, set, tag);
423         final NetworkStatsHistory existing = mStats.get(key);
424 
425         // update when no existing, or when bucket duration changed
426         NetworkStatsHistory updated = null;
427         if (existing == null) {
428             updated = new NetworkStatsHistory(mBucketDuration, 10);
429         } else if (existing.getBucketDuration() != mBucketDuration) {
430             updated = new NetworkStatsHistory(existing, mBucketDuration);
431         }
432 
433         if (updated != null) {
434             mStats.put(key, updated);
435             return updated;
436         } else {
437             return existing;
438         }
439     }
440 
441     @Override
read(InputStream in)442     public void read(InputStream in) throws IOException {
443         final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE);
444         read(dataIn);
445     }
446 
read(DataInput in)447     private void read(DataInput in) throws IOException {
448         // verify file magic header intact
449         final int magic = in.readInt();
450         if (magic != FILE_MAGIC) {
451             throw new ProtocolException("unexpected magic: " + magic);
452         }
453 
454         final int version = in.readInt();
455         switch (version) {
456             case VERSION_UNIFIED_INIT: {
457                 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
458                 final int identSize = in.readInt();
459                 for (int i = 0; i < identSize; i++) {
460                     final NetworkIdentitySet ident = new NetworkIdentitySet(in);
461 
462                     final int size = in.readInt();
463                     for (int j = 0; j < size; j++) {
464                         final int uid = in.readInt();
465                         final int set = in.readInt();
466                         final int tag = in.readInt();
467 
468                         final Key key = new Key(ident, uid, set, tag);
469                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
470                         recordHistory(key, history);
471                     }
472                 }
473                 break;
474             }
475             default: {
476                 throw new ProtocolException("unexpected version: " + version);
477             }
478         }
479     }
480 
481     @Override
write(OutputStream out)482     public void write(OutputStream out) throws IOException {
483         final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE);
484         write(dataOut);
485         dataOut.flush();
486     }
487 
write(DataOutput out)488     private void write(DataOutput out) throws IOException {
489         // cluster key lists grouped by ident
490         final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
491         for (Key key : mStats.keySet()) {
492             ArrayList<Key> keys = keysByIdent.get(key.ident);
493             if (keys == null) {
494                 keys = Lists.newArrayList();
495                 keysByIdent.put(key.ident, keys);
496             }
497             keys.add(key);
498         }
499 
500         out.writeInt(FILE_MAGIC);
501         out.writeInt(VERSION_UNIFIED_INIT);
502 
503         out.writeInt(keysByIdent.size());
504         for (NetworkIdentitySet ident : keysByIdent.keySet()) {
505             final ArrayList<Key> keys = keysByIdent.get(ident);
506             ident.writeToStream(out);
507 
508             out.writeInt(keys.size());
509             for (Key key : keys) {
510                 final NetworkStatsHistory history = mStats.get(key);
511                 out.writeInt(key.uid);
512                 out.writeInt(key.set);
513                 out.writeInt(key.tag);
514                 history.writeToStream(out);
515             }
516         }
517     }
518 
519     @Deprecated
readLegacyNetwork(File file)520     public void readLegacyNetwork(File file) throws IOException {
521         final AtomicFile inputFile = new AtomicFile(file);
522 
523         DataInputStream in = null;
524         try {
525             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
526 
527             // verify file magic header intact
528             final int magic = in.readInt();
529             if (magic != FILE_MAGIC) {
530                 throw new ProtocolException("unexpected magic: " + magic);
531             }
532 
533             final int version = in.readInt();
534             switch (version) {
535                 case VERSION_NETWORK_INIT: {
536                     // network := size *(NetworkIdentitySet NetworkStatsHistory)
537                     final int size = in.readInt();
538                     for (int i = 0; i < size; i++) {
539                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
540                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
541 
542                         final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
543                         recordHistory(key, history);
544                     }
545                     break;
546                 }
547                 default: {
548                     throw new ProtocolException("unexpected version: " + version);
549                 }
550             }
551         } catch (FileNotFoundException e) {
552             // missing stats is okay, probably first boot
553         } finally {
554             IoUtils.closeQuietly(in);
555         }
556     }
557 
558     @Deprecated
readLegacyUid(File file, boolean onlyTags)559     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
560         final AtomicFile inputFile = new AtomicFile(file);
561 
562         DataInputStream in = null;
563         try {
564             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
565 
566             // verify file magic header intact
567             final int magic = in.readInt();
568             if (magic != FILE_MAGIC) {
569                 throw new ProtocolException("unexpected magic: " + magic);
570             }
571 
572             final int version = in.readInt();
573             switch (version) {
574                 case VERSION_UID_INIT: {
575                     // uid := size *(UID NetworkStatsHistory)
576 
577                     // drop this data version, since we don't have a good
578                     // mapping into NetworkIdentitySet.
579                     break;
580                 }
581                 case VERSION_UID_WITH_IDENT: {
582                     // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
583 
584                     // drop this data version, since this version only existed
585                     // for a short time.
586                     break;
587                 }
588                 case VERSION_UID_WITH_TAG:
589                 case VERSION_UID_WITH_SET: {
590                     // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
591                     final int identSize = in.readInt();
592                     for (int i = 0; i < identSize; i++) {
593                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
594 
595                         final int size = in.readInt();
596                         for (int j = 0; j < size; j++) {
597                             final int uid = in.readInt();
598                             final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
599                                     : SET_DEFAULT;
600                             final int tag = in.readInt();
601 
602                             final Key key = new Key(ident, uid, set, tag);
603                             final NetworkStatsHistory history = new NetworkStatsHistory(in);
604 
605                             if ((tag == TAG_NONE) != onlyTags) {
606                                 recordHistory(key, history);
607                             }
608                         }
609                     }
610                     break;
611                 }
612                 default: {
613                     throw new ProtocolException("unexpected version: " + version);
614                 }
615             }
616         } catch (FileNotFoundException e) {
617             // missing stats is okay, probably first boot
618         } finally {
619             IoUtils.closeQuietly(in);
620         }
621     }
622 
623     /**
624      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
625      * moving any {@link NetworkStats#TAG_NONE} series to
626      * {@link TrafficStats#UID_REMOVED}.
627      */
removeUids(int[] uids)628     public void removeUids(int[] uids) {
629         final ArrayList<Key> knownKeys = Lists.newArrayList();
630         knownKeys.addAll(mStats.keySet());
631 
632         // migrate all UID stats into special "removed" bucket
633         for (Key key : knownKeys) {
634             if (ArrayUtils.contains(uids, key.uid)) {
635                 // only migrate combined TAG_NONE history
636                 if (key.tag == TAG_NONE) {
637                     final NetworkStatsHistory uidHistory = mStats.get(key);
638                     final NetworkStatsHistory removedHistory = findOrCreateHistory(
639                             key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
640                     removedHistory.recordEntireHistory(uidHistory);
641                 }
642                 mStats.remove(key);
643                 mDirty = true;
644             }
645         }
646     }
647 
noteRecordedHistory(long startMillis, long endMillis, long totalBytes)648     private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
649         if (startMillis < mStartMillis) mStartMillis = startMillis;
650         if (endMillis > mEndMillis) mEndMillis = endMillis;
651         mTotalBytes += totalBytes;
652         mDirty = true;
653     }
654 
estimateBuckets()655     private int estimateBuckets() {
656         return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
657                 / mBucketDuration);
658     }
659 
getSortedKeys()660     private ArrayList<Key> getSortedKeys() {
661         final ArrayList<Key> keys = Lists.newArrayList();
662         keys.addAll(mStats.keySet());
663         Collections.sort(keys);
664         return keys;
665     }
666 
dump(IndentingPrintWriter pw)667     public void dump(IndentingPrintWriter pw) {
668         for (Key key : getSortedKeys()) {
669             pw.print("ident="); pw.print(key.ident.toString());
670             pw.print(" uid="); pw.print(key.uid);
671             pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
672             pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
673 
674             final NetworkStatsHistory history = mStats.get(key);
675             pw.increaseIndent();
676             history.dump(pw, true);
677             pw.decreaseIndent();
678         }
679     }
680 
dumpDebug(ProtoOutputStream proto, long tag)681     public void dumpDebug(ProtoOutputStream proto, long tag) {
682         final long start = proto.start(tag);
683 
684         for (Key key : getSortedKeys()) {
685             final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
686 
687             // Key
688             final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
689             key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY);
690             proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
691             proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
692             proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
693             proto.end(startKey);
694 
695             // Value
696             final NetworkStatsHistory history = mStats.get(key);
697             history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY);
698             proto.end(startStats);
699         }
700 
701         proto.end(start);
702     }
703 
dumpCheckin(PrintWriter pw, long start, long end)704     public void dumpCheckin(PrintWriter pw, long start, long end) {
705         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
706         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
707         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth");
708         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt");
709     }
710 
711     /**
712      * Dump all contained stats that match requested parameters, but group
713      * together all matching {@link NetworkTemplate} under a single prefix.
714      */
dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, String groupPrefix)715     private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
716             String groupPrefix) {
717         final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
718 
719         // Walk through all history, grouping by matching network templates
720         for (int i = 0; i < mStats.size(); i++) {
721             final Key key = mStats.keyAt(i);
722             final NetworkStatsHistory value = mStats.valueAt(i);
723 
724             if (!templateMatches(groupTemplate, key.ident)) continue;
725             if (key.set >= NetworkStats.SET_DEBUG_START) continue;
726 
727             final Key groupKey = new Key(null, key.uid, key.set, key.tag);
728             NetworkStatsHistory groupHistory = grouped.get(groupKey);
729             if (groupHistory == null) {
730                 groupHistory = new NetworkStatsHistory(value.getBucketDuration());
731                 grouped.put(groupKey, groupHistory);
732             }
733             groupHistory.recordHistory(value, start, end);
734         }
735 
736         for (int i = 0; i < grouped.size(); i++) {
737             final Key key = grouped.keyAt(i);
738             final NetworkStatsHistory value = grouped.valueAt(i);
739 
740             if (value.size() == 0) continue;
741 
742             pw.print("c,");
743             pw.print(groupPrefix); pw.print(',');
744             pw.print(key.uid); pw.print(',');
745             pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
746             pw.print(key.tag);
747             pw.println();
748 
749             value.dumpCheckin(pw);
750         }
751     }
752 
753     /**
754      * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
755      * in the given {@link NetworkIdentitySet}.
756      */
templateMatches(NetworkTemplate template, NetworkIdentitySet identSet)757     private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
758         for (NetworkIdentity ident : identSet) {
759             if (template.matches(ident)) {
760                 return true;
761             }
762         }
763         return false;
764     }
765 
766     private static class Key implements Comparable<Key> {
767         public final NetworkIdentitySet ident;
768         public final int uid;
769         public final int set;
770         public final int tag;
771 
772         private final int hashCode;
773 
Key(NetworkIdentitySet ident, int uid, int set, int tag)774         public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
775             this.ident = ident;
776             this.uid = uid;
777             this.set = set;
778             this.tag = tag;
779             hashCode = Objects.hash(ident, uid, set, tag);
780         }
781 
782         @Override
hashCode()783         public int hashCode() {
784             return hashCode;
785         }
786 
787         @Override
equals(Object obj)788         public boolean equals(Object obj) {
789             if (obj instanceof Key) {
790                 final Key key = (Key) obj;
791                 return uid == key.uid && set == key.set && tag == key.tag
792                         && Objects.equals(ident, key.ident);
793             }
794             return false;
795         }
796 
797         @Override
compareTo(Key another)798         public int compareTo(Key another) {
799             int res = 0;
800             if (ident != null && another.ident != null) {
801                 res = ident.compareTo(another.ident);
802             }
803             if (res == 0) {
804                 res = Integer.compare(uid, another.uid);
805             }
806             if (res == 0) {
807                 res = Integer.compare(set, another.set);
808             }
809             if (res == 0) {
810                 res = Integer.compare(tag, another.tag);
811             }
812             return res;
813         }
814     }
815 }
816