Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f0fdec66 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Track non-telecom calls in telecom metrics.

Track non-telecom call in the telecom metrics; this helps to get an idea
of adoption of the Telecom APIs.  We track both non-telecom calls which
have no phone account and those with a phone account to get an idea of
where apps are avoiding using Telecom despite having integration.

Flag: com.android.server.telecom.flags.enable_call_audio_watchdog
Test: Added unit tests.
Test: Performed manual testing.
Bug: 384570270
Change-Id: I611691819d75b50d0a6f98cd02b8fc4434cad4ad
parent fb4f7594
Loading
Loading
Loading
Loading
+21 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
    }

    /**
@@ -562,6 +566,7 @@ public class CallAudioWatchdog extends CallsManagerListenerBase {
            }
            if (!session.hasMediaResources()) {
                mLocalLog.log(session.toString());
                maybeLogMetrics(session);
                mCommunicationSessions.remove(uid);
            }
        }
@@ -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();
                }
            }
@@ -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());
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -673,7 +673,8 @@ public class CallsManager extends Call.ListenerBase
                                return -1;
                            }
                        }
                    }, clockProxy, mHandler);
                    }, clockProxy, mHandler,
                    featureFlags.telecomMetricsSupport() ? metricsController : null);
        } else {
            mCallAudioWatchDog = null;
        }
+24 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
+81 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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<>();
@@ -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
@@ -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,