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

Commit 1796396e authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

Merge "[le audio] Add Broadcast feature stack metrics" into main

parents e9157ecf fb400ba5
Loading
Loading
Loading
Loading
+207 −3
Original line number Diff line number Diff line
@@ -50,13 +50,16 @@ import android.os.Binder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Pair;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
@@ -73,6 +76,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
@@ -131,6 +135,8 @@ public class BassClientStateMachine extends StateMachine {
    private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing();
    private final List<Pair<ScanResult, Integer>> mSourceSyncRequestsQueue =
            new ArrayList<Pair<ScanResult, Integer>>();
    private final Map<Integer, LeAudioBroadcastSyncStats> mBroadcastSyncStats =
            new LinkedHashMap<>();

    @VisibleForTesting
    final List<BluetoothGattCharacteristic> mBroadcastCharacteristics =
@@ -204,6 +210,94 @@ public class BassClientStateMachine extends StateMachine {
        }
    }

    private static class LeAudioBroadcastSyncStats {
        private BluetoothDevice mDevice;
        private boolean mIsLocalBroadcast;
        private int mBroadcastId;
        private long mSourceAddTime;
        private long mSourcePaSyncedTime;
        private long mSourceBisSyncedTime;
        private int mSyncStatus;

        LeAudioBroadcastSyncStats(
                BluetoothDevice sink,
                BluetoothLeBroadcastMetadata metadata,
                boolean isLocalBroadcast,
                long startTime) {
            this.mDevice = sink;
            this.mIsLocalBroadcast = isLocalBroadcast;
            this.mBroadcastId = metadata.getBroadcastId();
            this.mSourceAddTime = startTime;
            this.mSourcePaSyncedTime = 0;
            this.mSourceBisSyncedTime = 0;
            this.mSyncStatus =
                    BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_SYNC_REQUESTED;
        }

        public void updatePaSyncedTime(long paSyncedTime) {
            if (mSourcePaSyncedTime == 0) {
                mSourcePaSyncedTime = paSyncedTime;
            }
        }

        public void updateBisSyncedTime(long bisSyncedTime) {
            if (mSourceBisSyncedTime == 0) {
                mSourceBisSyncedTime = bisSyncedTime;
            }
        }

        public void updateSyncStatus(int status) {
            if (mSyncStatus
                    != BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_AUDIO_SYNC_SUCCESS) {
                Log.d(
                        TAG,
                        "logBroadcastSyncMetrics: updating from state: "
                                + mSyncStatus
                                + " to "
                                + status);
                mSyncStatus = status;
            }
        }

        public void logBroadcastSyncMetrics(long stopTime) {
            long syncDurationMs =
                    (mSourceBisSyncedTime > 0) ? (stopTime - mSourceBisSyncedTime) : 0;
            long latencyPaSyncedMs =
                    (mSourcePaSyncedTime > 0) ? (mSourcePaSyncedTime - mSourceAddTime) : 0;
            long latencyBisSyncedMs =
                    (mSourcePaSyncedTime > 0 && mSourceBisSyncedTime > 0)
                            ? (mSourceBisSyncedTime - mSourcePaSyncedTime)
                            : 0;

            Log.d(
                    TAG,
                    "logBroadcastSyncMetrics: broadcastId: "
                            + mBroadcastId
                            + ", isLocalBroadcast: "
                            + mIsLocalBroadcast
                            + ", syncDurationMs: "
                            + syncDurationMs
                            + ", latencyPaSyncedMs: "
                            + latencyPaSyncedMs
                            + ", latencyBisSyncedMs: "
                            + latencyBisSyncedMs
                            + ", syncStatus: "
                            + mSyncStatus);

            MetricsLogger.getInstance()
                    .logLeAudioBroadcastAudioSync(
                            mDevice,
                            mBroadcastId,
                            mIsLocalBroadcast,
                            syncDurationMs,
                            latencyPaSyncedMs,
                            latencyBisSyncedMs,
                            mSyncStatus);
        }
    }

    static BassClientStateMachine make(
            BluetoothDevice device,
            BassClientService svc,
@@ -259,6 +353,7 @@ public class BassClientStateMachine extends StateMachine {
        mPendingRemove.clear();
        mPeriodicAdvCallbacksMap.clear();
        mSourceSyncRequestsQueue.clear();
        mBroadcastSyncStats.clear();
    }

    Boolean hasPendingSourceOperation() {
@@ -785,6 +880,94 @@ public class BassClientStateMachine extends StateMachine {
        }
    }

    private void processSyncStateChangeStats(BluetoothLeBroadcastReceiveState recvState) {
        int sourceId = recvState.getSourceId();
        BluetoothLeBroadcastMetadata metaData = getCurrentBroadcastMetadata(sourceId);
        if (metaData == null) {
            Log.d(TAG, "No metadata for sourceId, skip logging");
            return;
        }

        int broadcastId = metaData.getBroadcastId();
        LeAudioBroadcastSyncStats syncStats = mBroadcastSyncStats.get(broadcastId);
        if (syncStats == null) {
            Log.d(TAG, "No stats for sourceId, skip logging");
            return;
        }

        // Check PA state
        int paState = recvState.getPaSyncState();
        if (paState == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) {
            syncStats.updatePaSyncedTime(SystemClock.elapsedRealtime());
            syncStats.updateSyncStatus(
                    BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_PA_SYNC_SUCCESS);
            // Continue to check other fields
        } else if (paState
                == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE) {
            syncStats.updateSyncStatus(
                    BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_PA_SYNC_FAILED);
            // Update the failure state and continue to let sinks retry PA sync
        } else if (paState == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_NO_PAST) {
            // NO PAST server will not attempt to PA sync, log the failure
            logBroadcastSyncStatsWithStatus(
                    broadcastId,
                    BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_PA_SYNC_NO_PAST);
            return;
        }

        // Check Big encrypt state
        int bigEncryptState = recvState.getBigEncryptionState();
        if (bigEncryptState == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) {
            logBroadcastSyncStatsWithStatus(
                    broadcastId,
                    BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_BIG_DECRYPT_FAILED);
            return;
        }

        // Check Bis state
        for (int i = 0; i < recvState.getNumSubgroups(); i++) {
            Long bisState = recvState.getBisSyncState().get(i);
            if (bisState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG
                    && bisState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS) {
                // Any bis synced, update status and break
                syncStats.updateBisSyncedTime(SystemClock.elapsedRealtime());
                syncStats.updateSyncStatus(
                        BluetoothStatsLog
                                .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_AUDIO_SYNC_SUCCESS);
                break;
            } else if (bisState == BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
                logBroadcastSyncStatsWithStatus(
                        broadcastId,
                        BluetoothStatsLog
                                .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_AUDIO_SYNC_FAILED);
                break;
            }
        }
    }

    private void logBroadcastSyncStatsWithStatus(int broadcastId, int status) {
        LeAudioBroadcastSyncStats syncStats = mBroadcastSyncStats.remove(broadcastId);
        if (syncStats != null) {
            if (status
                    != BluetoothStatsLog
                            .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_UNKNOWN) {
                syncStats.updateSyncStatus(status);
            }
            syncStats.logBroadcastSyncMetrics(SystemClock.elapsedRealtime());
        }
    }

    private void logAllBroadcastSyncStatsAndCleanup() {
        for (LeAudioBroadcastSyncStats syncStats : mBroadcastSyncStats.values()) {
            syncStats.logBroadcastSyncMetrics(SystemClock.elapsedRealtime());
        }
        mBroadcastSyncStats.clear();
    }

    private void checkAndUpdateBroadcastCode(BluetoothLeBroadcastReceiveState recvState) {
        log("checkAndUpdateBroadcastCode");
        // Whenever receive state indicated code requested, assistant should set the broadcast code
@@ -985,22 +1168,33 @@ public class BassClientStateMachine extends StateMachine {
                }
                checkAndUpdateBroadcastCode(recvState);
                processPASyncState(recvState);
                processSyncStateChangeStats(recvState);
            } else {
                if (recvState.getSourceDevice() == null
                        || recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
                    BluetoothDevice removedDevice = oldRecvState.getSourceDevice();
                    log("sourceInfo removal " + removedDevice);
                    int prevSourceId = oldRecvState.getSourceId();
                    if (!Flags.leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
                        cancelActiveSync(
                                mService.getSyncHandleForBroadcastId(recvState.getBroadcastId()));
                    }
                    setCurrentBroadcastMetadata(oldRecvState.getSourceId(), null);
                    BluetoothLeBroadcastMetadata metaData =
                            getCurrentBroadcastMetadata(prevSourceId);
                    if (metaData != null) {
                        logBroadcastSyncStatsWithStatus(
                                metaData.getBroadcastId(),
                                BluetoothStatsLog
                                        .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_UNKNOWN);
                    }

                    setCurrentBroadcastMetadata(prevSourceId, null);
                    if (mPendingSourceToSwitch != null) {
                        // Source remove is triggered by switch source request
                        mService.getCallbacks()
                                .notifySourceRemoved(
                                        mDevice,
                                        oldRecvState.getSourceId(),
                                        prevSourceId,
                                        BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);
                        log("Switching to new source");
                        Message message = obtainMessage(ADD_BCAST_SOURCE);
@@ -1010,7 +1204,7 @@ public class BassClientStateMachine extends StateMachine {
                        mService.getCallbacks()
                                .notifySourceRemoved(
                                        mDevice,
                                        oldRecvState.getSourceId(),
                                        prevSourceId,
                                        BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
                    }
                } else {
@@ -1027,6 +1221,7 @@ public class BassClientStateMachine extends StateMachine {
                                    BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
                    checkAndUpdateBroadcastCode(recvState);
                    processPASyncState(recvState);
                    processSyncStateChangeStats(recvState);

                    if (isPendingRemove(recvState.getSourceId())) {
                        Message message = obtainMessage(REMOVE_BCAST_SOURCE);
@@ -1435,6 +1630,7 @@ public class BassClientStateMachine extends StateMachine {
                            + mDevice
                            + "): "
                            + messageWhatToString(getCurrentMessage().what));
            logAllBroadcastSyncStatsAndCleanup();
            clearCharsCache();
            mNextSourceId = 0;
            removeDeferredMessages(DISCONNECT);
@@ -1993,6 +2189,14 @@ public class BassClientStateMachine extends StateMachine {
                        break;
                    }
                    if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
                        mBroadcastSyncStats.put(
                                metaData.getBroadcastId(),
                                new LeAudioBroadcastSyncStats(
                                        mDevice,
                                        metaData,
                                        mService.isLocalBroadcast(metaData),
                                        SystemClock.elapsedRealtime()));

                        writeBassControlPoint(addSourceInfo);
                        mPendingOperation = message.what;
                        mPendingMetadata = metaData;
+50 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.util.proto.ProtoOutputStream;

import androidx.annotation.RequiresApi;

import com.android.bluetooth.bass_client.BassConstants;
import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
import com.android.bluetooth.BluetoothMetricsProto.BluetoothRemoteDeviceInformation;
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
@@ -583,4 +584,53 @@ public class MetricsLogger {
        }
        return digest.digest(name.getBytes(StandardCharsets.UTF_8));
    }

    /** Logs LE Audio Broadcast audio session. */
    public void logLeAudioBroadcastAudioSession(
            int broadcastId,
            int[] audioQuality,
            int groupSize,
            long sessionDurationMs,
            long latencySessionConfiguredMs,
            long latencySessionStreamingMs,
            int sessionStatus) {
        if (!mInitialized) {
            return;
        }

        BluetoothStatsLog.write(
                BluetoothStatsLog.BROADCAST_AUDIO_SESSION_REPORTED,
                broadcastId,
                audioQuality.length,
                audioQuality,
                groupSize,
                sessionDurationMs,
                latencySessionConfiguredMs,
                latencySessionStreamingMs,
                sessionStatus);
    }

    /** Logs LE Audio Broadcast audio sync. */
    public void logLeAudioBroadcastAudioSync(
            BluetoothDevice device,
            int broadcastId,
            boolean isLocalBroadcast,
            long syncDurationMs,
            long latencyPaSyncMs,
            long latencyBisSyncMs,
            int syncStatus) {
        if (!mInitialized) {
            return;
        }

        BluetoothStatsLog.write(
                BluetoothStatsLog.BROADCAST_AUDIO_SYNC_REPORTED,
                isLocalBroadcast ? broadcastId : BassConstants.INVALID_BROADCAST_ID,
                isLocalBroadcast,
                syncDurationMs,
                latencyPaSyncMs,
                latencyBisSyncMs,
                syncStatus,
                getRemoteDeviceInfoProto(device));
    }
}
+188 −0

File changed.

Preview size limit exceeded, changes collapsed.

+279 −74

File changed.

Preview size limit exceeded, changes collapsed.

+46 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
package com.android.bluetooth.le_audio;

import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
import static com.android.bluetooth.bass_client.BassConstants.INVALID_BROADCAST_ID;

import static org.mockito.Mockito.*;

@@ -42,6 +43,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.bass_client.BassClientService;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.flags.Flags;
@@ -90,6 +92,7 @@ public class LeAudioBroadcastServiceTest {
    @Mock private LeAudioTmapGattServer mTmapGattServer;
    @Mock private BassClientService mBassClientService;
    @Mock private TbsService mTbsService;
    @Mock private MetricsLogger mMetricsLogger;
    @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();
    @Spy private ServiceFactory mServiceFactory = new ServiceFactory();

@@ -210,6 +213,8 @@ public class LeAudioBroadcastServiceTest {
        doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager();

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        MetricsLogger.getInstance();
        MetricsLogger.setInstanceForTesting(mMetricsLogger);

        LeAudioBroadcasterNativeInterface.setInstance(mLeAudioBroadcasterNativeInterface);
        LeAudioNativeInterface.setInstance(mLeAudioNativeInterface);
@@ -247,6 +252,7 @@ public class LeAudioBroadcastServiceTest {
        stopService();
        LeAudioBroadcasterNativeInterface.setInstance(null);
        LeAudioNativeInterface.setInstance(null);
        MetricsLogger.setInstanceForTesting(null);
        TestUtils.clearAdapterService(mAdapterService);
        reset(mAudioManager);
    }
@@ -322,6 +328,8 @@ public class LeAudioBroadcastServiceTest {
    }

    void verifyBroadcastStopped(int broadcastId) {
        Mockito.clearInvocations(mMetricsLogger);

        mService.stopBroadcast(broadcastId);
        verify(mLeAudioBroadcasterNativeInterface, times(1)).stopBroadcast(eq(broadcastId));

@@ -340,6 +348,16 @@ public class LeAudioBroadcastServiceTest {

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());

        // Verify broadcast audio session is logged when session stopped
        verify(mMetricsLogger, times(1))
                .logLeAudioBroadcastAudioSession(
                        eq(broadcastId),
                        eq(new int[] {0x2}), // STATS_SESSION_AUDIO_QUALITY_HIGH
                        eq(0),
                        anyLong(),
                        anyLong(),
                        anyLong(),
                        eq(0x3)); // STATS_SESSION_SETUP_STATUS_STREAMING
        Assert.assertTrue(mOnBroadcastStoppedCalled);
        Assert.assertFalse(mOnBroadcastStopFailedCalled);
    }
@@ -388,6 +406,7 @@ public class LeAudioBroadcastServiceTest {
        synchronized (mService.mBroadcastCallbacks) {
            mService.mBroadcastCallbacks.register(mCallbacks);
        }
        Mockito.clearInvocations(mMetricsLogger);

        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
@@ -420,6 +439,17 @@ public class LeAudioBroadcastServiceTest {

        TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());

        // Verify broadcast audio session is logged when session failed to create
        verify(mMetricsLogger, times(1))
                .logLeAudioBroadcastAudioSession(
                        eq(INVALID_BROADCAST_ID),
                        eq(new int[] {0x2}), // STATS_SESSION_AUDIO_QUALITY_HIGH
                        eq(0),
                        eq(0L),
                        eq(0L),
                        eq(0L),
                        eq(0x4)); // STATS_SESSION_SETUP_STATUS_CREATED_FAILED

        Assert.assertFalse(mOnBroadcastStartedCalled);
        Assert.assertTrue(mOnBroadcastStartFailedCalled);
    }
@@ -434,6 +464,7 @@ public class LeAudioBroadcastServiceTest {
        synchronized (mService.mBroadcastCallbacks) {
            mService.mBroadcastCallbacks.register(mCallbacks);
        }
        Mockito.clearInvocations(mMetricsLogger);

        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
@@ -481,6 +512,21 @@ public class LeAudioBroadcastServiceTest {
        // Check if broadcast is destroyed after timeout
        verify(mLeAudioBroadcasterNativeInterface, timeout(CREATE_BROADCAST_TIMEOUT_MS).times(1))
                .destroyBroadcast(eq(broadcastId));

        state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED);
        state_event.valueInt1 = broadcastId;
        mService.messageFromNative(state_event);

        // Verify broadcast audio session is logged when session failed to stream
        verify(mMetricsLogger, times(1))
                .logLeAudioBroadcastAudioSession(
                        eq(broadcastId),
                        eq(new int[] {0x2}), // STATS_SESSION_AUDIO_QUALITY_HIGH
                        eq(0),
                        anyLong(),
                        anyLong(),
                        eq(0L),
                        eq(0x5)); // STATS_SESSION_SETUP_STATUS_STREAMING_FAILED
    }

    @Test