blob: 0ba859232bb1c5ab6d891b3474f421849f088f3b [file] [log] [blame]
Hall Liu32587202015-11-18 11:10:08 -08001/*
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
17package com.android.server.telecom;
18
Hall Liuecd74a52016-01-12 15:26:36 -080019import android.os.Parcelable;
20import android.telecom.ParcelableCallAnalytics;
Hall Liu32587202015-11-18 11:10:08 -080021import android.telecom.DisconnectCause;
22
23import com.android.internal.annotations.VisibleForTesting;
Hall Liuecd74a52016-01-12 15:26:36 -080024import com.android.internal.telephony.CallerInfo;
Hall Liu32587202015-11-18 11:10:08 -080025import com.android.internal.util.IndentingPrintWriter;
26
27import java.util.HashMap;
28import 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 */
34public class Analytics {
35 public static class CallInfo {
36 void setCallStartTime(long startTime) {
37 }
38
39 void setCallEndTime(long endTime) {
40 }
41
42 void setCallIsAdditional(boolean isAdditional) {
43 }
44
45 void setCallIsInterrupted(boolean isInterrupted) {
46 }
47
48 void setCallDisconnectCause(DisconnectCause disconnectCause) {
49 }
50
51 void addCallTechnology(int callTechnology) {
52 }
53
54 void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
55 }
56
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
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
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
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
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
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
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
144 public void addCallTechnology(int callTechnology) {
145 Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology);
146 this.callTechnologies |= callTechnology;
147 }
148
149 @Override
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
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
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
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'
Hall Liuecd74a52016-01-12 15:26:36 -0800179 + " connectionService: " + connectionService + '\n'
Hall Liu32587202015-11-18 11:10:08 -0800180 + "}\n";
181 }
182
Hall Liuecd74a52016-01-12 15:26:36 -0800183 public ParcelableCallAnalytics toParcelableAnalytics() {
184 // Rounds up to the nearest second.
185 long callDuration = endTime == 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
Hall Liu32587202015-11-18 11:10:08 -0800204 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
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
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
Hall Liuecd74a52016-01-12 15:26:36 -0800240 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;
Hall Liu32587202015-11-18 11:10:08 -0800243
244 // Constants for call technology
Hall Liuecd74a52016-01-12 15:26:36 -0800245 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;
Hall Liu32587202015-11-18 11:10:08 -0800252
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
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
Hall Liuecd74a52016-01-12 15:26:36 -0800265 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
Hall Liu32587202015-11-18 11:10:08 -0800279 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
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 */
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}