1 /*
2  * Copyright (C) 2020 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.internal.telephony.metrics;
18 
19 import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
20 import com.android.telephony.Rlog;
21 
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Set;
27 
28 /**
29  * Uses a HashMap to track carrier and RAT usage, in terms of duration and number of calls.
30  *
31  * <p>This tracker is first used by each call, and then used for aggregating usages across calls.
32  *
33  * <p>This class is not thread-safe. Callers (from {@link PersistAtomsStorage} and {@link
34  * VoiceCallSessionStats}) should ensure each instance of this class is accessed by only one thread
35  * at a time.
36  */
37 public class VoiceCallRatTracker {
38     private static final String TAG = VoiceCallRatTracker.class.getSimpleName();
39 
40     /** A Map holding all carrier-RAT combinations and corresponding durations and call counts. */
41     private final Map<Key, Value> mRatUsageMap = new HashMap<>();
42 
43     /** Tracks last carrier/RAT combination during a call. */
44     private Key mLastKey;
45 
46     /** Tracks the time when last carrier/RAT combination was updated. */
47     private long mLastKeyTimestampMillis;
48 
49     /** Creates an empty RAT tracker for each call. */
VoiceCallRatTracker()50     VoiceCallRatTracker() {
51         clear();
52     }
53 
54     /** Creates an RAT tracker from saved atoms at startup. */
fromProto(RawVoiceCallRatUsage[] usages)55     public static VoiceCallRatTracker fromProto(RawVoiceCallRatUsage[] usages) {
56         VoiceCallRatTracker tracker = new VoiceCallRatTracker();
57         if (usages == null) {
58             Rlog.e(TAG, "fromProto: usages=null");
59         } else {
60             Arrays.stream(usages).forEach(e -> tracker.addProto(e));
61         }
62         return tracker;
63     }
64 
65     /** Append the map to javanano persist atoms. */
toProto()66     public RawVoiceCallRatUsage[] toProto() {
67         return mRatUsageMap.entrySet().stream()
68                 .map(VoiceCallRatTracker::entryToProto)
69                 .toArray(RawVoiceCallRatUsage[]::new);
70     }
71 
72     /** Resets the tracker. */
clear()73     public void clear() {
74         mRatUsageMap.clear();
75         mLastKey = null;
76         mLastKeyTimestampMillis = 0L;
77     }
78 
79     /** Adds one RAT usage to the tracker during a call session. */
add(int carrierId, int rat, long timestampMillis, Set<Integer> connectionIds)80     public void add(int carrierId, int rat, long timestampMillis, Set<Integer> connectionIds) {
81         // both new RAT (different key) and new connections (same key) needs to be added
82         // set duration and connections (may have changed) for last RAT/carrier combination
83         if (mLastKey != null) {
84             long durationMillis = timestampMillis - mLastKeyTimestampMillis;
85             if (durationMillis < 0L) {
86                 Rlog.e(TAG, "add: durationMillis<0");
87                 durationMillis = 0L;
88             }
89             addToKey(mLastKey, durationMillis, connectionIds);
90         }
91 
92         // set connections for new RAT/carrier combination
93         Key key = new Key(carrierId, rat);
94         addToKey(key, 0L, connectionIds);
95 
96         mLastKey = key;
97         mLastKeyTimestampMillis = timestampMillis;
98     }
99 
100     /** Finalize the duration of the last RAT at the end of a call session. */
conclude(long timestampMillis)101     public void conclude(long timestampMillis) {
102         if (mLastKey != null) {
103             long durationMillis = timestampMillis - mLastKeyTimestampMillis;
104             if (durationMillis < 0L) {
105                 Rlog.e(TAG, "conclude: durationMillis<0");
106                 durationMillis = 0L;
107             }
108             Value value = mRatUsageMap.get(mLastKey);
109             if (value == null) {
110                 Rlog.e(TAG, "conclude: value=null && mLastKey!=null");
111             } else {
112                 value.durationMillis += durationMillis;
113             }
114             mRatUsageMap.values().stream().forEach(Value::endSession);
115         } else {
116             Rlog.e(TAG, "conclude: mLastKey=null");
117         }
118     }
119 
120     /** Merges this tracker with another instance created during a call session. */
mergeWith(VoiceCallRatTracker that)121     public VoiceCallRatTracker mergeWith(VoiceCallRatTracker that) {
122         if (that == null) {
123             Rlog.e(TAG, "mergeWith: attempting to merge with null", new Throwable());
124         } else {
125             that.mRatUsageMap.entrySet().stream()
126                     .forEach(
127                             e ->
128                                     this.mRatUsageMap.merge(
129                                             e.getKey(), e.getValue(), Value::mergeInPlace));
130         }
131         return this;
132     }
133 
addToKey(Key key, long durationMillis, Set<Integer> connectionIds)134     private void addToKey(Key key, long durationMillis, Set<Integer> connectionIds) {
135         Value value = mRatUsageMap.get(key);
136         if (value == null) {
137             mRatUsageMap.put(key, new Value(durationMillis, connectionIds));
138         } else {
139             value.add(durationMillis, connectionIds);
140         }
141     }
142 
addProto(RawVoiceCallRatUsage usage)143     private void addProto(RawVoiceCallRatUsage usage) {
144         mRatUsageMap.put(Key.fromProto(usage), Value.fromProto(usage));
145     }
146 
entryToProto(Map.Entry<Key, Value> entry)147     private static RawVoiceCallRatUsage entryToProto(Map.Entry<Key, Value> entry) {
148         Key key = entry.getKey();
149         Value value = entry.getValue();
150         RawVoiceCallRatUsage usage = new RawVoiceCallRatUsage();
151         usage.carrierId = key.carrierId;
152         usage.rat = key.rat;
153         if (value.mConnectionIds != null) {
154             Rlog.e(TAG, "call not concluded when converting to proto");
155         }
156         usage.totalDurationMillis = value.durationMillis;
157         usage.callCount = value.callCount;
158         return usage;
159     }
160 
161     /*
162      * NOTE: proto does not support message as map keys, and javanano generates mutable objects for
163      * keys anyways. So it is better to implement the key/value class in java.
164      */
165 
166     private static class Key {
167         public final int carrierId;
168         public final int rat;
169 
Key(int carrierId, int rat)170         Key(int carrierId, int rat) {
171             this.carrierId = carrierId;
172             this.rat = rat;
173         }
174 
fromProto(RawVoiceCallRatUsage usage)175         static Key fromProto(RawVoiceCallRatUsage usage) {
176             return new Key(usage.carrierId, usage.rat);
177         }
178 
hashCode()179         public int hashCode() {
180             return Objects.hash(carrierId, rat);
181         }
182 
equals(Object that)183         public boolean equals(Object that) {
184             if (that == null || that.getClass() != this.getClass()) {
185                 return false;
186             }
187             Key thatKey = (Key) that;
188             return thatKey.carrierId == this.carrierId && thatKey.rat == this.rat;
189         }
190     }
191 
192     private static class Value {
193         public long durationMillis;
194         public long callCount;
195 
196         private Set<Integer> mConnectionIds;
197 
Value(long durationMillis, Set<Integer> connectionIds)198         Value(long durationMillis, Set<Integer> connectionIds) {
199             this.durationMillis = durationMillis;
200             mConnectionIds = connectionIds;
201             callCount = 0L;
202         }
203 
Value(long durationMillis, long callCount)204         private Value(long durationMillis, long callCount) {
205             this.durationMillis = durationMillis;
206             mConnectionIds = null;
207             this.callCount = callCount;
208         }
209 
add(long durationMillis, Set<Integer> connectionIds)210         void add(long durationMillis, Set<Integer> connectionIds) {
211             this.durationMillis += durationMillis;
212             if (mConnectionIds != null) {
213                 mConnectionIds.addAll(connectionIds);
214             } else {
215                 Rlog.e(TAG, "Value: trying to add to concluded call");
216             }
217         }
218 
endSession()219         void endSession() {
220             if (mConnectionIds != null) {
221                 if (callCount != 0L) {
222                     Rlog.e(TAG, "Value: mConnectionIds!=null && callCount!=0");
223                 }
224                 callCount = mConnectionIds.size();
225                 mConnectionIds = null; // allow GC
226             }
227         }
228 
fromProto(RawVoiceCallRatUsage usage)229         static Value fromProto(RawVoiceCallRatUsage usage) {
230             Value value = new Value(usage.totalDurationMillis, usage.callCount);
231             return value;
232         }
233 
mergeInPlace(Value dest, Value src)234         static Value mergeInPlace(Value dest, Value src) {
235             if (src.mConnectionIds != null || dest.mConnectionIds != null) {
236                 Rlog.e(TAG, "Value: call not concluded yet when merging");
237             }
238             // NOTE: does not handle overflow since it is practically impossible
239             dest.durationMillis += src.durationMillis;
240             dest.callCount += src.callCount;
241             return dest;
242         }
243     }
244 }
245