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