Loading src/com/android/server/telecom/CallAudioWatchdog.java +21 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.metrics.TelecomMetricsController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading Loading @@ -367,15 +368,18 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { // Local logs for tracking non-telecom calls. private final LocalLog mLocalLog = new LocalLog(30); private final TelecomMetricsController mMetricsController; public CallAudioWatchdog(AudioManager audioManager, PhoneAccountRegistrarProxy phoneAccountRegistrarProxy, ClockProxy clockProxy, Handler handler) { Handler handler, TelecomMetricsController metricsController) { mPhoneAccountRegistrarProxy = phoneAccountRegistrarProxy; mClockProxy = clockProxy; mAudioManager = audioManager; mHandler = handler; mAudioManager.registerAudioPlaybackCallback(mWatchdogAudioPlayback, mHandler); mAudioManager.registerAudioRecordingCallback(mWatchdogAudioRecordCallack, mHandler); mMetricsController = metricsController; } /** Loading Loading @@ -562,6 +566,7 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { } if (!session.hasMediaResources()) { mLocalLog.log(session.toString()); maybeLogMetrics(session); mCommunicationSessions.remove(uid); } } Loading Loading @@ -648,6 +653,7 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { if (!session.hasMediaResources() && session.getTelecomCall() == null) { Log.i(this, "cleanupAttributeForSessions: removing session %s", session); mLocalLog.log(session.toString()); maybeLogMetrics(session); iterator.remove(); } } Loading Loading @@ -676,4 +682,18 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { map.put(key, theDefault); return theDefault; } /** * If this call has no associated Telecom {@link Call} and metrics are enabled, log this as a * non-telecom call. * @param session the session to log. */ private void maybeLogMetrics(CommunicationSession session) { if (mMetricsController != null && session.getTelecomCall() == null) { mMetricsController.getCallStats().onNonTelecomCallEnd( session.isBitSet(SESSION_ATTR_HAS_PHONE_ACCOUNT), session.getUid(), mClockProxy.elapsedRealtime() - session.getSessionStartMillis()); } } } src/com/android/server/telecom/CallsManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -677,7 +677,8 @@ public class CallsManager extends Call.ListenerBase return -1; } } }, clockProxy, mHandler); }, clockProxy, mHandler, featureFlags.telecomMetricsSupport() ? metricsController : null); } else { mCallAudioWatchDog = null; } Loading src/com/android/server/telecom/metrics/CallStats.java +24 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYP import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_VOIP_API; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_INCOMING; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_OUTGOING; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN; Loading Loading @@ -173,6 +175,28 @@ public class CallStats extends TelecomPulledAtom { }); } /** * Used for logging non-telecom calls that have no associated {@link Call}. This is inferred * from the {@link com.android.server.telecom.CallAudioWatchdog}. * * @param hasTelecomSupport {@code true} if the app making the non-telecom call has Telecom * support (i.e. has a phone account}; * {@code false} otherwise. * @param uid The uid of the app making the call. * @param durationMillis The duration of the call, in millis. */ public void onNonTelecomCallEnd(final boolean hasTelecomSupport, final int uid, final long durationMillis) { post(() -> log(CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN, false /* isExternalCall */, false /* isEmergencyCall */, false /* hasMultipleAudioDevices */, hasTelecomSupport ? CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT : CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP, uid, (int) durationMillis)); } private int getAccountType(PhoneAccount account) { if (account == null) { return CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN; Loading tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java +81 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,14 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; Loading @@ -40,6 +47,8 @@ import android.util.ArrayMap; import com.android.server.telecom.Call; import com.android.server.telecom.CallAudioWatchdog; import com.android.server.telecom.ClockProxy; import com.android.server.telecom.metrics.CallStats; import com.android.server.telecom.metrics.TelecomMetricsController; import org.junit.After; import org.junit.Before; Loading @@ -62,6 +71,7 @@ import java.util.Optional; public class CallAudioWatchdogTest extends TelecomTestCase { private static final String TEST_CALL_ID = "TC@90210"; private static final int TEST_APP_1_UID = 10001; private static final int TEST_APP_2_UID = 10002; private static final PhoneAccountHandle TEST_APP_1_HANDLE = new PhoneAccountHandle( new ComponentName("com.app1.package", "class1"), "1"); private static final ArrayMap<Integer, PhoneAccountHandle> TEST_UID_TO_PHAC = new ArrayMap<>(); Loading @@ -86,15 +96,19 @@ public class CallAudioWatchdogTest extends TelecomTestCase { }; @Mock private ClockProxy mClockProxy; @Mock private TelecomMetricsController mMetricsController; @Mock private CallStats mCallStats; private CallAudioWatchdog mCallAudioWatchdog; @Override @Before public void setUp() throws Exception { super.setUp(); when(mMetricsController.getCallStats()).thenReturn(mCallStats); when(mClockProxy.elapsedRealtime()).thenReturn(0L); TEST_UID_TO_PHAC.put(TEST_APP_1_UID, TEST_APP_1_HANDLE); mCallAudioWatchdog = new CallAudioWatchdog(mComponentContextFixture.getAudioManager(), mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */); mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */, mMetricsController); } @Override Loading Loading @@ -177,9 +191,75 @@ public class CallAudioWatchdogTest extends TelecomTestCase { when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); // Ensure that a call with telecom support but which did not use Telecom gets logged to // metrics as a non-telecom call. verify(mCallStats).onNonTelecomCallEnd(eq(true), eq(TEST_APP_1_UID), eq(1000L)); } /** * Verifies ability of the audio watchdog to track non-telecom calls where there is no Telecom * integration. */ @Test public void testNonTelecomCallMetricsTracking() { var client1Recording = makeAudioRecordingConfiguration(TEST_APP_2_UID, 1); var theRecords = Arrays.asList(client1Recording); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(theRecords); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID)); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID)); // This should log as a non-telecom call with no telecom support. verify(mCallStats).onNonTelecomCallEnd(eq(false), eq(TEST_APP_2_UID), eq(1000L)); } /** * Verifies that if a call known to Telecom is added, that we don't try to track it in the * non-telecom metrics. */ @Test public void testTelecomCallMetricsTracking() { var client1Recording = makeAudioRecordingConfiguration(TEST_APP_1_UID, 1); var theRecords = Arrays.asList(client1Recording); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(theRecords); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); Call mockCall = mock(Call.class); when(mockCall.isSelfManaged()).thenReturn(true); when(mockCall.isExternalCall()).thenReturn(false); when(mockCall.getTargetPhoneAccount()).thenReturn(TEST_APP_1_HANDLE); when(mockCall.getId()).thenReturn("90210"); mCallAudioWatchdog.onCallAdded(mockCall); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); mCallAudioWatchdog.onCallRemoved(mockCall); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); // We should not log a non-telecom call. Note; we are purposely NOT trying to check if a // Telecom call metric is logged here since that is done elsewhere and this unit test is // only testing CallAudioWatchdog in isolation. verify(mCallStats, never()).onNonTelecomCallEnd(anyBoolean(), anyInt(), anyLong()); } private AudioPlaybackConfiguration makeAudioPlaybackConfiguration(int clientUid, Loading Loading
src/com/android/server/telecom/CallAudioWatchdog.java +21 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.metrics.TelecomMetricsController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading Loading @@ -367,15 +368,18 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { // Local logs for tracking non-telecom calls. private final LocalLog mLocalLog = new LocalLog(30); private final TelecomMetricsController mMetricsController; public CallAudioWatchdog(AudioManager audioManager, PhoneAccountRegistrarProxy phoneAccountRegistrarProxy, ClockProxy clockProxy, Handler handler) { Handler handler, TelecomMetricsController metricsController) { mPhoneAccountRegistrarProxy = phoneAccountRegistrarProxy; mClockProxy = clockProxy; mAudioManager = audioManager; mHandler = handler; mAudioManager.registerAudioPlaybackCallback(mWatchdogAudioPlayback, mHandler); mAudioManager.registerAudioRecordingCallback(mWatchdogAudioRecordCallack, mHandler); mMetricsController = metricsController; } /** Loading Loading @@ -562,6 +566,7 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { } if (!session.hasMediaResources()) { mLocalLog.log(session.toString()); maybeLogMetrics(session); mCommunicationSessions.remove(uid); } } Loading Loading @@ -648,6 +653,7 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { if (!session.hasMediaResources() && session.getTelecomCall() == null) { Log.i(this, "cleanupAttributeForSessions: removing session %s", session); mLocalLog.log(session.toString()); maybeLogMetrics(session); iterator.remove(); } } Loading Loading @@ -676,4 +682,18 @@ public class CallAudioWatchdog extends CallsManagerListenerBase { map.put(key, theDefault); return theDefault; } /** * If this call has no associated Telecom {@link Call} and metrics are enabled, log this as a * non-telecom call. * @param session the session to log. */ private void maybeLogMetrics(CommunicationSession session) { if (mMetricsController != null && session.getTelecomCall() == null) { mMetricsController.getCallStats().onNonTelecomCallEnd( session.isBitSet(SESSION_ATTR_HAS_PHONE_ACCOUNT), session.getUid(), mClockProxy.elapsedRealtime() - session.getSessionStartMillis()); } } }
src/com/android/server/telecom/CallsManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -677,7 +677,8 @@ public class CallsManager extends Call.ListenerBase return -1; } } }, clockProxy, mHandler); }, clockProxy, mHandler, featureFlags.telecomMetricsSupport() ? metricsController : null); } else { mCallAudioWatchDog = null; } Loading
src/com/android/server/telecom/metrics/CallStats.java +24 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYP import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_VOIP_API; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_INCOMING; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_OUTGOING; import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN; Loading Loading @@ -173,6 +175,28 @@ public class CallStats extends TelecomPulledAtom { }); } /** * Used for logging non-telecom calls that have no associated {@link Call}. This is inferred * from the {@link com.android.server.telecom.CallAudioWatchdog}. * * @param hasTelecomSupport {@code true} if the app making the non-telecom call has Telecom * support (i.e. has a phone account}; * {@code false} otherwise. * @param uid The uid of the app making the call. * @param durationMillis The duration of the call, in millis. */ public void onNonTelecomCallEnd(final boolean hasTelecomSupport, final int uid, final long durationMillis) { post(() -> log(CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN, false /* isExternalCall */, false /* isEmergencyCall */, false /* hasMultipleAudioDevices */, hasTelecomSupport ? CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT : CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP, uid, (int) durationMillis)); } private int getAccountType(PhoneAccount account) { if (account == null) { return CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN; Loading
tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java +81 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,14 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; Loading @@ -40,6 +47,8 @@ import android.util.ArrayMap; import com.android.server.telecom.Call; import com.android.server.telecom.CallAudioWatchdog; import com.android.server.telecom.ClockProxy; import com.android.server.telecom.metrics.CallStats; import com.android.server.telecom.metrics.TelecomMetricsController; import org.junit.After; import org.junit.Before; Loading @@ -62,6 +71,7 @@ import java.util.Optional; public class CallAudioWatchdogTest extends TelecomTestCase { private static final String TEST_CALL_ID = "TC@90210"; private static final int TEST_APP_1_UID = 10001; private static final int TEST_APP_2_UID = 10002; private static final PhoneAccountHandle TEST_APP_1_HANDLE = new PhoneAccountHandle( new ComponentName("com.app1.package", "class1"), "1"); private static final ArrayMap<Integer, PhoneAccountHandle> TEST_UID_TO_PHAC = new ArrayMap<>(); Loading @@ -86,15 +96,19 @@ public class CallAudioWatchdogTest extends TelecomTestCase { }; @Mock private ClockProxy mClockProxy; @Mock private TelecomMetricsController mMetricsController; @Mock private CallStats mCallStats; private CallAudioWatchdog mCallAudioWatchdog; @Override @Before public void setUp() throws Exception { super.setUp(); when(mMetricsController.getCallStats()).thenReturn(mCallStats); when(mClockProxy.elapsedRealtime()).thenReturn(0L); TEST_UID_TO_PHAC.put(TEST_APP_1_UID, TEST_APP_1_HANDLE); mCallAudioWatchdog = new CallAudioWatchdog(mComponentContextFixture.getAudioManager(), mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */); mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */, mMetricsController); } @Override Loading Loading @@ -177,9 +191,75 @@ public class CallAudioWatchdogTest extends TelecomTestCase { when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); // Ensure that a call with telecom support but which did not use Telecom gets logged to // metrics as a non-telecom call. verify(mCallStats).onNonTelecomCallEnd(eq(true), eq(TEST_APP_1_UID), eq(1000L)); } /** * Verifies ability of the audio watchdog to track non-telecom calls where there is no Telecom * integration. */ @Test public void testNonTelecomCallMetricsTracking() { var client1Recording = makeAudioRecordingConfiguration(TEST_APP_2_UID, 1); var theRecords = Arrays.asList(client1Recording); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(theRecords); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID)); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID)); // This should log as a non-telecom call with no telecom support. verify(mCallStats).onNonTelecomCallEnd(eq(false), eq(TEST_APP_2_UID), eq(1000L)); } /** * Verifies that if a call known to Telecom is added, that we don't try to track it in the * non-telecom metrics. */ @Test public void testTelecomCallMetricsTracking() { var client1Recording = makeAudioRecordingConfiguration(TEST_APP_1_UID, 1); var theRecords = Arrays.asList(client1Recording); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(theRecords); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); Call mockCall = mock(Call.class); when(mockCall.isSelfManaged()).thenReturn(true); when(mockCall.isExternalCall()).thenReturn(false); when(mockCall.getTargetPhoneAccount()).thenReturn(TEST_APP_1_HANDLE); when(mockCall.getId()).thenReturn("90210"); mCallAudioWatchdog.onCallAdded(mockCall); when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations()) .thenReturn(Collections.EMPTY_LIST); when(mClockProxy.elapsedRealtime()).thenReturn(1000L); mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged( Collections.EMPTY_LIST); assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); mCallAudioWatchdog.onCallRemoved(mockCall); assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID)); // We should not log a non-telecom call. Note; we are purposely NOT trying to check if a // Telecom call metric is logged here since that is done elsewhere and this unit test is // only testing CallAudioWatchdog in isolation. verify(mCallStats, never()).onNonTelecomCallEnd(anyBoolean(), anyInt(), anyLong()); } private AudioPlaybackConfiguration makeAudioPlaybackConfiguration(int clientUid, Loading