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