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