1 /* 2 * Copyright (C) 2016 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.tests; 18 19 import android.content.Context; 20 import android.os.Build; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.telecom.CallAudioState; 24 import android.telecom.Connection; 25 import android.telecom.DisconnectCause; 26 import android.telecom.InCallService; 27 import android.telecom.Log; 28 import android.telecom.Logging.EventManager; 29 import android.telecom.ParcelableCallAnalytics; 30 import android.telecom.TelecomAnalytics; 31 import android.telecom.TelecomManager; 32 import android.telecom.VideoCallImpl; 33 import android.telecom.VideoProfile; 34 import android.support.test.filters.FlakyTest; 35 import android.test.suitebuilder.annotation.MediumTest; 36 import android.test.suitebuilder.annotation.SmallTest; 37 import android.util.Base64; 38 39 import com.android.internal.util.IndentingPrintWriter; 40 import com.android.server.telecom.Analytics; 41 import com.android.server.telecom.CallAudioRouteStateMachine; 42 import com.android.server.telecom.LogUtils; 43 import com.android.server.telecom.nano.TelecomLogClass; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.JUnit4; 50 51 import java.io.PrintWriter; 52 import java.io.StringWriter; 53 import java.util.Arrays; 54 import java.util.HashSet; 55 import java.util.LinkedList; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.Set; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.TimeUnit; 61 62 import static org.junit.Assert.assertEquals; 63 import static org.junit.Assert.assertFalse; 64 import static org.junit.Assert.assertNotNull; 65 import static org.junit.Assert.assertNull; 66 import static org.junit.Assert.assertTrue; 67 import static org.mockito.Matchers.any; 68 import static org.mockito.Matchers.anyInt; 69 import static org.mockito.Mockito.doAnswer; 70 import static org.mockito.Mockito.mock; 71 72 @RunWith(JUnit4.class) 73 public class AnalyticsTests extends TelecomSystemTest { 74 @Override 75 @Before 76 public void setUp() throws Exception { 77 super.setUp(); 78 } 79 80 @Override 81 @After 82 public void tearDown() throws Exception { 83 super.tearDown(); 84 } 85 86 @MediumTest 87 @Test 88 public void testAnalyticsSingleCall() throws Exception { 89 IdPair testCall = startAndMakeActiveIncomingCall( 90 "650-555-1212", 91 mPhoneAccountA0.getAccountHandle(), 92 mConnectionServiceFixtureA); 93 94 Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData(); 95 96 assertTrue(analyticsMap.containsKey(testCall.mCallId)); 97 98 Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId); 99 assertTrue(callAnalytics.startTime > 0); 100 assertEquals(0, callAnalytics.endTime); 101 assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics.callDirection); 102 assertFalse(callAnalytics.isInterrupted); 103 assertNull(callAnalytics.callTerminationReason); 104 assertEquals(mConnectionServiceComponentNameA.flattenToShortString(), 105 callAnalytics.connectionService); 106 107 mConnectionServiceFixtureA. 108 sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR); 109 110 analyticsMap = Analytics.cloneData(); 111 callAnalytics = analyticsMap.get(testCall.mCallId); 112 assertTrue(callAnalytics.endTime > 0); 113 assertNotNull(callAnalytics.callTerminationReason); 114 assertEquals(DisconnectCause.ERROR, callAnalytics.callTerminationReason.getCode()); 115 116 StringWriter sr = new StringWriter(); 117 IndentingPrintWriter ip = new IndentingPrintWriter(sr, " "); 118 Analytics.dump(ip); 119 String dumpResult = sr.toString(); 120 String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall", 121 "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"}; 122 for (String field : expectedFields) { 123 assertTrue(dumpResult.contains(field)); 124 } 125 } 126 127 @FlakyTest 128 @MediumTest 129 @Test 130 public void testAnalyticsDumping() throws Exception { 131 Analytics.reset(); 132 IdPair testCall = startAndMakeActiveIncomingCall( 133 "650-555-1212", 134 mPhoneAccountA0.getAccountHandle(), 135 mConnectionServiceFixtureA); 136 137 mConnectionServiceFixtureA. 138 sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR); 139 Analytics.CallInfoImpl expectedAnalytics = Analytics.cloneData().get(testCall.mCallId); 140 141 TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE); 142 List<ParcelableCallAnalytics> analyticsList = tm.dumpAnalytics().getCallAnalytics(); 143 144 assertEquals(1, analyticsList.size()); 145 ParcelableCallAnalytics pCA = analyticsList.get(0); 146 147 assertTrue(Math.abs(expectedAnalytics.startTime - pCA.getStartTimeMillis()) < 148 ParcelableCallAnalytics.MILLIS_IN_5_MINUTES); 149 assertEquals(0, pCA.getStartTimeMillis() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES); 150 assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) - 151 pCA.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND); 152 assertEquals(0, pCA.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND); 153 154 assertEquals(expectedAnalytics.callDirection, pCA.getCallType()); 155 assertEquals(expectedAnalytics.isAdditionalCall, pCA.isAdditionalCall()); 156 assertEquals(expectedAnalytics.isInterrupted, pCA.isInterrupted()); 157 assertEquals(expectedAnalytics.callTechnologies, pCA.getCallTechnologies()); 158 assertEquals(expectedAnalytics.callTerminationReason.getCode(), 159 pCA.getCallTerminationCode()); 160 assertEquals(expectedAnalytics.connectionService, pCA.getConnectionService()); 161 List<ParcelableCallAnalytics.AnalyticsEvent> analyticsEvents = pCA.analyticsEvents(); 162 Set<Integer> capturedEvents = new HashSet<>(); 163 for (ParcelableCallAnalytics.AnalyticsEvent e : analyticsEvents) { 164 capturedEvents.add(e.getEventName()); 165 assertIsRoundedToOneSigFig(e.getTimeSinceLastEvent()); 166 } 167 assertTrue(capturedEvents.contains(ParcelableCallAnalytics.AnalyticsEvent.SET_ACTIVE)); 168 assertTrue(capturedEvents.contains( 169 ParcelableCallAnalytics.AnalyticsEvent.FILTERING_INITIATED)); 170 } 171 172 @MediumTest 173 @Test 174 public void testAnalyticsTwoCalls() throws Exception { 175 IdPair testCall1 = startAndMakeActiveIncomingCall( 176 "650-555-1212", 177 mPhoneAccountA0.getAccountHandle(), 178 mConnectionServiceFixtureA); 179 IdPair testCall2 = startAndMakeActiveOutgoingCall( 180 "650-555-1213", 181 mPhoneAccountA0.getAccountHandle(), 182 mConnectionServiceFixtureA); 183 184 Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData(); 185 assertTrue(analyticsMap.containsKey(testCall1.mCallId)); 186 assertTrue(analyticsMap.containsKey(testCall2.mCallId)); 187 188 Analytics.CallInfoImpl callAnalytics1 = analyticsMap.get(testCall1.mCallId); 189 Analytics.CallInfoImpl callAnalytics2 = analyticsMap.get(testCall2.mCallId); 190 assertTrue(callAnalytics1.startTime > 0); 191 assertTrue(callAnalytics2.startTime > 0); 192 assertEquals(0, callAnalytics1.endTime); 193 assertEquals(0, callAnalytics2.endTime); 194 195 assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection); 196 assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection); 197 198 assertTrue(callAnalytics1.isInterrupted); 199 assertTrue(callAnalytics2.isAdditionalCall); 200 201 assertNull(callAnalytics1.callTerminationReason); 202 assertNull(callAnalytics2.callTerminationReason); 203 204 assertEquals(mConnectionServiceComponentNameA.flattenToShortString(), 205 callAnalytics1.connectionService); 206 assertEquals(mConnectionServiceComponentNameA.flattenToShortString(), 207 callAnalytics1.connectionService); 208 209 mConnectionServiceFixtureA. 210 sendSetDisconnected(testCall2.mConnectionId, DisconnectCause.REMOTE); 211 mConnectionServiceFixtureA. 212 sendSetDisconnected(testCall1.mConnectionId, DisconnectCause.ERROR); 213 214 analyticsMap = Analytics.cloneData(); 215 callAnalytics1 = analyticsMap.get(testCall1.mCallId); 216 callAnalytics2 = analyticsMap.get(testCall2.mCallId); 217 assertTrue(callAnalytics1.endTime > 0); 218 assertTrue(callAnalytics2.endTime > 0); 219 assertNotNull(callAnalytics1.callTerminationReason); 220 assertNotNull(callAnalytics2.callTerminationReason); 221 assertEquals(DisconnectCause.ERROR, callAnalytics1.callTerminationReason.getCode()); 222 assertEquals(DisconnectCause.REMOTE, callAnalytics2.callTerminationReason.getCode()); 223 } 224 225 @MediumTest 226 @Test 227 public void testAnalyticsVideo() throws Exception { 228 Analytics.reset(); 229 IdPair callIds = startAndMakeActiveOutgoingCall( 230 "650-555-1212", 231 mPhoneAccountA0.getAccountHandle(), 232 mConnectionServiceFixtureA); 233 234 CountDownLatch counter = new CountDownLatch(1); 235 InCallService.VideoCall.Callback callback = mock(InCallService.VideoCall.Callback.class); 236 237 doAnswer(invocation -> { 238 counter.countDown(); 239 return null; 240 }).when(callback) 241 .onSessionModifyResponseReceived(anyInt(), any(VideoProfile.class), 242 any(VideoProfile.class)); 243 244 mConnectionServiceFixtureA.sendSetVideoProvider( 245 mConnectionServiceFixtureA.mLatestConnectionId); 246 InCallService.VideoCall videoCall = 247 mInCallServiceFixtureX.getCall(callIds.mCallId).getVideoCallImpl( 248 mInCallServiceComponentNameX.getPackageName(), Build.VERSION.SDK_INT); 249 videoCall.registerCallback(callback); 250 ((VideoCallImpl) videoCall).setVideoState(VideoProfile.STATE_BIDIRECTIONAL); 251 252 videoCall.sendSessionModifyRequest(new VideoProfile(VideoProfile.STATE_RX_ENABLED)); 253 counter.await(10000, TimeUnit.MILLISECONDS); 254 255 StringWriter sw = new StringWriter(); 256 PrintWriter pw = new PrintWriter(sw); 257 Analytics.dumpToEncodedProto(pw, new String[]{}); 258 TelecomLogClass.TelecomLog analyticsProto = 259 TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT)); 260 261 assertEquals(1, analyticsProto.callLogs.length); 262 TelecomLogClass.VideoEvent[] videoEvents = analyticsProto.callLogs[0].videoEvents; 263 assertEquals(2, videoEvents.length); 264 265 assertEquals(Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, videoEvents[0].getEventName()); 266 assertEquals(VideoProfile.STATE_RX_ENABLED, videoEvents[0].getVideoState()); 267 assertEquals(-1, videoEvents[0].getTimeSinceLastEventMillis()); 268 269 assertEquals(Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE, 270 videoEvents[1].getEventName()); 271 assertEquals(VideoProfile.STATE_RX_ENABLED, videoEvents[1].getVideoState()); 272 assertIsRoundedToOneSigFig(videoEvents[1].getTimeSinceLastEventMillis()); 273 } 274 275 @SmallTest 276 @Test 277 public void testAnalyticsRounding() { 278 long[] testVals = {0, -1, -10, -100, -57836, 1, 10, 100, 1000, 458457}; 279 long[] expected = {0, -1, -10, -100, -60000, 1, 10, 100, 1000, 500000}; 280 for (int i = 0; i < testVals.length; i++) { 281 assertEquals(expected[i], Analytics.roundToOneSigFig(testVals[i])); 282 } 283 } 284 285 @SmallTest 286 @Test 287 public void testAnalyticsLogSessionTiming() throws Exception { 288 long minTime = 50; 289 Log.startSession(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL); 290 Thread.sleep(minTime); 291 Log.endSession(); 292 TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE); 293 List<TelecomAnalytics.SessionTiming> sessions = tm.dumpAnalytics().getSessionTimings(); 294 sessions.stream() 295 .filter(s -> LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL.equals( 296 Analytics.sSessionIdToLogSession.get(s.getKey()))) 297 .forEach(s -> assertTrue(s.getTime() >= minTime)); 298 } 299 300 @MediumTest 301 @Test 302 public void testAnalyticsDumpToProto() throws Exception { 303 Analytics.reset(); 304 IdPair testCall = startAndMakeActiveIncomingCall( 305 "650-555-1212", 306 mPhoneAccountA0.getAccountHandle(), 307 mConnectionServiceFixtureA); 308 309 mConnectionServiceFixtureA. 310 sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR); 311 Analytics.CallInfoImpl expectedAnalytics = Analytics.cloneData().get(testCall.mCallId); 312 313 StringWriter sw = new StringWriter(); 314 PrintWriter pw = new PrintWriter(sw); 315 Analytics.dumpToEncodedProto(pw, new String[]{}); 316 TelecomLogClass.TelecomLog analyticsProto = 317 TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT)); 318 319 assertEquals(1, analyticsProto.callLogs.length); 320 TelecomLogClass.CallLog callLog = analyticsProto.callLogs[0]; 321 322 assertTrue(Math.abs(expectedAnalytics.startTime - callLog.getStartTime5Min()) < 323 ParcelableCallAnalytics.MILLIS_IN_5_MINUTES); 324 assertEquals(0, callLog.getStartTime5Min() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES); 325 assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) - 326 callLog.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND); 327 assertEquals(0, 328 callLog.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND); 329 330 assertEquals(expectedAnalytics.callDirection, callLog.getType()); 331 assertEquals(expectedAnalytics.isAdditionalCall, callLog.getIsAdditionalCall()); 332 assertEquals(expectedAnalytics.isInterrupted, callLog.getIsInterrupted()); 333 assertEquals(expectedAnalytics.callTechnologies, callLog.getCallTechnologies()); 334 assertEquals(expectedAnalytics.callTerminationReason.getCode(), 335 callLog.getCallTerminationCode()); 336 assertEquals(expectedAnalytics.connectionService, callLog.connectionService[0]); 337 TelecomLogClass.Event[] analyticsEvents = callLog.callEvents; 338 Set<Integer> capturedEvents = new HashSet<>(); 339 for (TelecomLogClass.Event e : analyticsEvents) { 340 capturedEvents.add(e.getEventName()); 341 assertIsRoundedToOneSigFig(e.getTimeSinceLastEventMillis()); 342 } 343 assertTrue(capturedEvents.contains(ParcelableCallAnalytics.AnalyticsEvent.SET_ACTIVE)); 344 assertTrue(capturedEvents.contains( 345 ParcelableCallAnalytics.AnalyticsEvent.FILTERING_INITIATED)); 346 } 347 348 @MediumTest 349 @Test 350 public void testAnalyticsAudioRoutes() throws Exception { 351 Analytics.reset(); 352 IdPair testCall = startAndMakeActiveIncomingCall( 353 "650-555-1212", 354 mPhoneAccountA0.getAccountHandle(), 355 mConnectionServiceFixtureA); 356 List<Integer> audioRoutes = new LinkedList<>(); 357 358 waitForHandlerAction( 359 mTelecomSystem.getCallsManager().getCallAudioManager() 360 .getCallAudioRouteStateMachine().getHandler(), 361 TEST_TIMEOUT); 362 waitForHandlerAction( 363 mTelecomSystem.getCallsManager().getCallAudioManager() 364 .getCallAudioModeStateMachine().getHandler(), 365 TEST_TIMEOUT); 366 audioRoutes.add(mInCallServiceFixtureX.mCallAudioState.getRoute()); 367 mInCallServiceFixtureX.getInCallAdapter().setAudioRoute(CallAudioState.ROUTE_SPEAKER, null); 368 waitForHandlerAction( 369 mTelecomSystem.getCallsManager().getCallAudioManager() 370 .getCallAudioRouteStateMachine().getHandler(), 371 TEST_TIMEOUT); 372 waitForHandlerAction( 373 mTelecomSystem.getCallsManager().getCallAudioManager() 374 .getCallAudioModeStateMachine().getHandler(), 375 TEST_TIMEOUT); 376 audioRoutes.add(CallAudioState.ROUTE_SPEAKER); 377 378 Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData(); 379 assertTrue(analyticsMap.containsKey(testCall.mCallId)); 380 381 Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId); 382 List<EventManager.Event> events = callAnalytics.callEvents.getEvents(); 383 for (int route : audioRoutes) { 384 String logEvent = CallAudioRouteStateMachine.AUDIO_ROUTE_TO_LOG_EVENT.get(route); 385 assertTrue(events.stream().anyMatch(event -> event.eventId.equals(logEvent))); 386 } 387 } 388 389 @MediumTest 390 @Test 391 public void testAnalyticsConnectionProperties() throws Exception { 392 Analytics.reset(); 393 IdPair testCall = startAndMakeActiveIncomingCall( 394 "650-555-1212", 395 mPhoneAccountA0.getAccountHandle(), 396 mConnectionServiceFixtureA); 397 398 int properties1 = Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE 399 | Connection.PROPERTY_WIFI 400 | Connection.PROPERTY_EMERGENCY_CALLBACK_MODE; 401 int properties2 = Connection.PROPERTY_HIGH_DEF_AUDIO 402 | Connection.PROPERTY_WIFI; 403 int expectedProperties = properties1 | properties2; 404 405 mConnectionServiceFixtureA.mConnectionById.get(testCall.mConnectionId).properties = 406 properties1; 407 mConnectionServiceFixtureA.sendSetConnectionProperties(testCall.mConnectionId); 408 mConnectionServiceFixtureA.mConnectionById.get(testCall.mConnectionId).properties = 409 properties2; 410 mConnectionServiceFixtureA.sendSetConnectionProperties(testCall.mConnectionId); 411 412 mConnectionServiceFixtureA. 413 sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR); 414 415 StringWriter sw = new StringWriter(); 416 PrintWriter pw = new PrintWriter(sw); 417 Analytics.dumpToEncodedProto(pw, new String[]{}); 418 TelecomLogClass.TelecomLog analyticsProto = 419 TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT)); 420 421 assertEquals(expectedProperties, 422 analyticsProto.callLogs[0].getConnectionProperties() & expectedProperties); 423 } 424 425 @SmallTest 426 @Test 427 public void testAnalyticsMaxSize() throws Exception { 428 Analytics.reset(); 429 for (int i = 0; i < Analytics.MAX_NUM_CALLS_TO_STORE * 2; i++) { 430 Analytics.initiateCallAnalytics(String.valueOf(i), Analytics.INCOMING_DIRECTION) 431 .addCallTechnology(i); 432 } 433 434 StringWriter sw = new StringWriter(); 435 PrintWriter pw = new PrintWriter(sw); 436 Analytics.dumpToEncodedProto(pw, new String[]{}); 437 TelecomLogClass.TelecomLog analyticsProto = 438 TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT)); 439 440 assertEquals(Analytics.MAX_NUM_CALLS_TO_STORE, analyticsProto.callLogs.length); 441 assertEquals(Arrays.stream(analyticsProto.callLogs) 442 .filter(x -> x.getCallTechnologies() < 100) 443 .count(), 0); 444 } 445 446 private void assertIsRoundedToOneSigFig(long x) { 447 assertEquals(x, Analytics.roundToOneSigFig(x)); 448 } 449 } 450