1 /* 2 * Copyright (C) 2015 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.telecom; 18 19 import android.os.Parcelable; 20 import android.telecom.ParcelableCallAnalytics; 21 import android.telecom.DisconnectCause; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.telephony.CallerInfo; 25 import com.android.internal.util.IndentingPrintWriter; 26 27 import java.util.HashMap; 28 import java.util.Map; 29 30 /** 31 * A class that collects and stores data on how calls are being made, in order to 32 * aggregate these into useful statistics. 33 */ 34 public class Analytics { 35 public static class CallInfo { setCallStartTime(long startTime)36 void setCallStartTime(long startTime) { 37 } 38 setCallEndTime(long endTime)39 void setCallEndTime(long endTime) { 40 } 41 setCallIsAdditional(boolean isAdditional)42 void setCallIsAdditional(boolean isAdditional) { 43 } 44 setCallIsInterrupted(boolean isInterrupted)45 void setCallIsInterrupted(boolean isInterrupted) { 46 } 47 setCallDisconnectCause(DisconnectCause disconnectCause)48 void setCallDisconnectCause(DisconnectCause disconnectCause) { 49 } 50 addCallTechnology(int callTechnology)51 void addCallTechnology(int callTechnology) { 52 } 53 setCreatedFromExistingConnection(boolean createdFromExistingConnection)54 void setCreatedFromExistingConnection(boolean createdFromExistingConnection) { 55 } 56 setCallConnectionService(String connectionServiceName)57 void setCallConnectionService(String connectionServiceName) { 58 } 59 } 60 61 /** 62 * A class that holds data associated with a call. 63 */ 64 @VisibleForTesting 65 public static class CallInfoImpl extends CallInfo { 66 public String callId; 67 public long startTime; // start time in milliseconds since the epoch. 0 if not yet set. 68 public long endTime; // end time in milliseconds since the epoch. 0 if not yet set. 69 public int callDirection; // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION, 70 // or OUTGOING_DIRECTION. 71 public boolean isAdditionalCall = false; // true if the call came in while another call was 72 // in progress or if the user dialed this call 73 // while in the middle of another call. 74 public boolean isInterrupted = false; // true if the call was interrupted by an incoming 75 // or outgoing call. 76 public int callTechnologies; // bitmask denoting which technologies a call used. 77 78 // true if the Telecom Call object was created from an existing connection via 79 // CallsManager#createCallForExistingConnection, for example, by ImsConference. 80 public boolean createdFromExistingConnection = false; 81 82 public DisconnectCause callTerminationReason; 83 public String connectionService; 84 public boolean isEmergency = false; 85 CallInfoImpl(String callId, int callDirection)86 CallInfoImpl(String callId, int callDirection) { 87 this.callId = callId; 88 startTime = 0; 89 endTime = 0; 90 this.callDirection = callDirection; 91 callTechnologies = 0; 92 connectionService = ""; 93 } 94 CallInfoImpl(CallInfoImpl other)95 CallInfoImpl(CallInfoImpl other) { 96 this.callId = other.callId; 97 this.startTime = other.startTime; 98 this.endTime = other.endTime; 99 this.callDirection = other.callDirection; 100 this.isAdditionalCall = other.isAdditionalCall; 101 this.isInterrupted = other.isInterrupted; 102 this.callTechnologies = other.callTechnologies; 103 this.createdFromExistingConnection = other.createdFromExistingConnection; 104 this.connectionService = other.connectionService; 105 this.isEmergency = other.isEmergency; 106 107 if (other.callTerminationReason != null) { 108 this.callTerminationReason = new DisconnectCause( 109 other.callTerminationReason.getCode(), 110 other.callTerminationReason.getLabel(), 111 other.callTerminationReason.getDescription(), 112 other.callTerminationReason.getReason(), 113 other.callTerminationReason.getTone()); 114 } else { 115 this.callTerminationReason = null; 116 } 117 } 118 119 @Override setCallStartTime(long startTime)120 public void setCallStartTime(long startTime) { 121 Log.d(TAG, "setting startTime for call " + callId + " to " + startTime); 122 this.startTime = startTime; 123 } 124 125 @Override setCallEndTime(long endTime)126 public void setCallEndTime(long endTime) { 127 Log.d(TAG, "setting endTime for call " + callId + " to " + endTime); 128 this.endTime = endTime; 129 } 130 131 @Override setCallIsAdditional(boolean isAdditional)132 public void setCallIsAdditional(boolean isAdditional) { 133 Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional); 134 this.isAdditionalCall = isAdditional; 135 } 136 137 @Override setCallIsInterrupted(boolean isInterrupted)138 public void setCallIsInterrupted(boolean isInterrupted) { 139 Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted); 140 this.isInterrupted = isInterrupted; 141 } 142 143 @Override addCallTechnology(int callTechnology)144 public void addCallTechnology(int callTechnology) { 145 Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology); 146 this.callTechnologies |= callTechnology; 147 } 148 149 @Override setCallDisconnectCause(DisconnectCause disconnectCause)150 public void setCallDisconnectCause(DisconnectCause disconnectCause) { 151 Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause); 152 this.callTerminationReason = disconnectCause; 153 } 154 155 @Override setCreatedFromExistingConnection(boolean createdFromExistingConnection)156 public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) { 157 Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to " 158 + createdFromExistingConnection); 159 this.createdFromExistingConnection = createdFromExistingConnection; 160 } 161 162 @Override setCallConnectionService(String connectionServiceName)163 public void setCallConnectionService(String connectionServiceName) { 164 Log.d(TAG, "setting connection service for call " + callId + ": " 165 + connectionServiceName); 166 this.connectionService = connectionServiceName; 167 } 168 169 @Override toString()170 public String toString() { 171 return "{\n" 172 + " startTime: " + startTime + '\n' 173 + " endTime: " + endTime + '\n' 174 + " direction: " + getCallDirectionString() + '\n' 175 + " isAdditionalCall: " + isAdditionalCall + '\n' 176 + " isInterrupted: " + isInterrupted + '\n' 177 + " callTechnologies: " + getCallTechnologiesAsString() + '\n' 178 + " callTerminationReason: " + getCallDisconnectReasonString() + '\n' 179 + " connectionService: " + connectionService + '\n' 180 + "}\n"; 181 } 182 toParcelableAnalytics()183 public ParcelableCallAnalytics toParcelableAnalytics() { 184 // Rounds up to the nearest second. 185 long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime; 186 callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ? 187 0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND); 188 return new ParcelableCallAnalytics( 189 // rounds down to nearest 5 minute mark 190 startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES, 191 callDuration, 192 callDirection, 193 isAdditionalCall, 194 isInterrupted, 195 callTechnologies, 196 callTerminationReason == null ? 197 ParcelableCallAnalytics.STILL_CONNECTED : 198 callTerminationReason.getCode(), 199 isEmergency, 200 connectionService, 201 createdFromExistingConnection); 202 } 203 getCallDirectionString()204 private String getCallDirectionString() { 205 switch (callDirection) { 206 case UNKNOWN_DIRECTION: 207 return "UNKNOWN"; 208 case INCOMING_DIRECTION: 209 return "INCOMING"; 210 case OUTGOING_DIRECTION: 211 return "OUTGOING"; 212 default: 213 return "UNKNOWN"; 214 } 215 } 216 getCallTechnologiesAsString()217 private String getCallTechnologiesAsString() { 218 StringBuilder s = new StringBuilder(); 219 s.append('['); 220 if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA "); 221 if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM "); 222 if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP "); 223 if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS "); 224 if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY "); 225 s.append(']'); 226 return s.toString(); 227 } 228 getCallDisconnectReasonString()229 private String getCallDisconnectReasonString() { 230 if (callTerminationReason != null) { 231 return callTerminationReason.toString(); 232 } else { 233 return "NOT SET"; 234 } 235 } 236 } 237 public static final String TAG = "TelecomAnalytics"; 238 239 // Constants for call direction 240 public static final int UNKNOWN_DIRECTION = ParcelableCallAnalytics.CALLTYPE_UNKNOWN; 241 public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING; 242 public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING; 243 244 // Constants for call technology 245 public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE; 246 public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE; 247 public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE; 248 public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE; 249 public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE; 250 251 public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND; 252 253 private static final Object sLock = new Object(); // Coarse lock for all of analytics 254 private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>(); 255 initiateCallAnalytics(String callId, int direction)256 public static CallInfo initiateCallAnalytics(String callId, int direction) { 257 Log.d(TAG, "Starting analytics for call " + callId); 258 CallInfoImpl callInfo = new CallInfoImpl(callId, direction); 259 synchronized (sLock) { 260 sCallIdToInfo.put(callId, callInfo); 261 } 262 return callInfo; 263 } 264 dumpToParcelableAnalytics()265 public static ParcelableCallAnalytics[] dumpToParcelableAnalytics() { 266 ParcelableCallAnalytics[] result; 267 synchronized (sLock) { 268 result = new ParcelableCallAnalytics[sCallIdToInfo.size()]; 269 int idx = 0; 270 for (CallInfoImpl entry : sCallIdToInfo.values()) { 271 result[idx] = entry.toParcelableAnalytics(); 272 idx++; 273 } 274 sCallIdToInfo.clear(); 275 } 276 return result; 277 } 278 dump(IndentingPrintWriter writer)279 public static void dump(IndentingPrintWriter writer) { 280 synchronized (sLock) { 281 for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) { 282 writer.printf("Call %s: ", entry.getKey()); 283 writer.println(entry.getValue().toString()); 284 } 285 } 286 } 287 reset()288 public static void reset() { 289 synchronized (sLock) { 290 sCallIdToInfo.clear(); 291 } 292 } 293 294 /** 295 * Returns a deep copy of callIdToInfo that's safe to read/write without synchronization 296 */ cloneData()297 public static Map<String, CallInfoImpl> cloneData() { 298 synchronized (sLock) { 299 Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size()); 300 for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) { 301 result.put(entry.getKey(), new CallInfoImpl(entry.getValue())); 302 } 303 return result; 304 } 305 } 306 } 307