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

Commit f3c53b8d authored by Jayden Kim's avatar Jayden Kim Committed by Gerrit Code Review
Browse files

Merge "Adds LE Scan Radio Duration"

parents d821d8f8 2dd09dcf
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
@@ -54,7 +56,7 @@ public class MetricsLogger {
    private static final HashMap<ProfileId, Integer> sProfileConnectionCounts = new HashMap<>();

    HashMap<Integer, Long> mCounters = new HashMap<>();
    private static MetricsLogger sInstance = null;
    private static volatile MetricsLogger sInstance = null;
    private Context mContext = null;
    private AlarmManager mAlarmManager = null;
    private boolean mInitialized = false;
@@ -81,6 +83,20 @@ public class MetricsLogger {
        return sInstance;
    }

    /**
     * Allow unit tests to substitute MetricsLogger with a test instance
     *
     * @param instance a test instance of the MetricsLogger
     */
    @VisibleForTesting
    public static void setInstanceForTesting(MetricsLogger instance) {
        Utils.enforceInstrumentationTestMode();
        synchronized (mLock) {
            Log.d(TAG, "setInstanceForTesting(), set to " + instance);
            sInstance = instance;
        }
    }

    public boolean isInitialized() {
        return mInitialized;
    }
+98 −1
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.bluetooth.gatt;

import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.os.BatteryStatsManager;
@@ -25,7 +26,9 @@ import android.os.WorkSource;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.util.WorkSourceUtil;
import com.android.internal.annotations.GuardedBy;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -49,8 +52,9 @@ import java.util.Objects;

    // Weight is the duty cycle of the scan mode
    static final int OPPORTUNISTIC_WEIGHT = 0;
    static final int SCREEN_OFF_LOW_POWER_WEIGHT = 5;
    static final int LOW_POWER_WEIGHT = 10;
    static final int AMBIENT_DISCOVERY_WEIGHT = 20;
    static final int AMBIENT_DISCOVERY_WEIGHT = 25;
    static final int BALANCED_WEIGHT = 25;
    static final int LOW_LATENCY_WEIGHT = 100;

@@ -67,6 +71,13 @@ import java.util.Objects;

    private final AdapterService mAdapterService;

    private static Object sLock = new Object();
    @GuardedBy("sLock")
    static long sRadioStartTime = 0;
    static int sRadioScanMode;
    static boolean sIsRadioStarted = false;
    static boolean sIsScreenOn = false;

    class LastScan {
        public long duration;
        public long suspendDuration;
@@ -329,6 +340,92 @@ import java.util.Objects;
                scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
    }

    static void initScanRadioState() {
        synchronized (sLock) {
            sIsRadioStarted = false;
        }
    }
    static boolean recordScanRadioStart(int scanMode) {
        synchronized (sLock) {
            if (sIsRadioStarted) {
                return false;
            }
            sRadioStartTime = SystemClock.elapsedRealtime();
            sRadioScanMode = scanMode;
            sIsRadioStarted = true;
        }
        return true;
    }

    static boolean recordScanRadioStop() {
        synchronized (sLock) {
            if (!sIsRadioStarted) {
                return false;
            }
            recordScanRadioDurationMetrics();
            sRadioStartTime = 0;
            sIsRadioStarted = false;
        }
        return true;
    }

    @GuardedBy("sLock")
    private static void recordScanRadioDurationMetrics() {
        if (!sIsRadioStarted) {
            return;
        }
        long currentTime = SystemClock.elapsedRealtime();
        long radioScanDuration = currentTime - sRadioStartTime;
        double scanWeight = getScanWeight(sRadioScanMode) * 0.01;
        long weightedDuration = (long) (radioScanDuration * scanWeight);

        if (weightedDuration > 0) {
            MetricsLogger.getInstance().cacheCount(
                    BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR, weightedDuration);
            if (sIsScreenOn) {
                MetricsLogger.getInstance().cacheCount(
                        BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON,
                        weightedDuration);
            } else {
                MetricsLogger.getInstance().cacheCount(
                        BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF,
                        weightedDuration);
            }
        }
    }

    private static int getScanWeight(int scanMode) {
        switch (scanMode) {
            case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
                return OPPORTUNISTIC_WEIGHT;
            case ScanSettings.SCAN_MODE_SCREEN_OFF:
                return SCREEN_OFF_LOW_POWER_WEIGHT;
            case ScanSettings.SCAN_MODE_LOW_POWER:
                return LOW_POWER_WEIGHT;
            case ScanSettings.SCAN_MODE_BALANCED:
            case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
            case ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED:
                return BALANCED_WEIGHT;
            case ScanSettings.SCAN_MODE_LOW_LATENCY:
                return LOW_LATENCY_WEIGHT;
            default:
                return LOW_POWER_WEIGHT;
        }
    }

    static void setScreenState(boolean isScreenOn) {
        synchronized (sLock) {
            if (sIsScreenOn == isScreenOn) {
                return;
            }
            if (sIsRadioStarted) {
                recordScanRadioDurationMetrics();
                sRadioStartTime = SystemClock.elapsedRealtime();
            }
            sIsScreenOn = isScreenOn;
        }
    }

    synchronized void recordScanSuspend(int scannerId) {
        LastScan scan = getScanFromScannerId(scannerId);
        if (scan == null || scan.isSuspended) {
+19 −0
Original line number Diff line number Diff line
@@ -193,6 +193,8 @@ public class ScanManager {
            mDm.registerDisplayListener(mDisplayListener, null);
        }
        mScreenOn = isScreenOn();
        AppScanStats.initScanRadioState();
        AppScanStats.setScreenState(mScreenOn);
        if (mActivityManager != null) {
            mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
                    FOREGROUND_IMPORTANCE_CUTOFF);
@@ -562,6 +564,7 @@ public class ScanManager {
        }

        void handleScreenOff() {
            AppScanStats.setScreenState(false);
            if (!mScreenOn) {
                return;
            }
@@ -854,6 +857,7 @@ public class ScanManager {
        }

        void handleScreenOn() {
            AppScanStats.setScreenState(true);
            if (mScreenOn) {
                return;
            }
@@ -1042,6 +1046,9 @@ public class ScanManager {
                    int scanWindow = Utils.millsToUnit(scanWindowMs);
                    int scanInterval = Utils.millsToUnit(scanIntervalMs);
                    mNativeInterface.gattClientScan(false);
                    if (!AppScanStats.recordScanRadioStop()) {
                        Log.w(TAG, "There is no scan radio to stop");
                    }
                    if (DBG) {
                        Log.d(TAG, "Start gattClientScanNative with"
                                + " old scanMode " + mLastConfiguredScanSetting
@@ -1053,6 +1060,9 @@ public class ScanManager {
                    mNativeInterface.gattSetScanParameters(client.scannerId, scanInterval,
                            scanWindow);
                    mNativeInterface.gattClientScan(true);
                    if (!AppScanStats.recordScanRadioStart(curScanSetting)) {
                        Log.w(TAG, "Scan radio already started");
                    }
                    mLastConfiguredScanSetting = curScanSetting;
                }
            } else {
@@ -1092,6 +1102,9 @@ public class ScanManager {
                    Log.d(TAG, "start gattClientScanNative from startRegularScan()");
                }
                mNativeInterface.gattClientScan(true);
                if (!AppScanStats.recordScanRadioStart(client.settings.getScanMode())) {
                    Log.w(TAG, "Scan radio already started");
                }
            }
        }

@@ -1315,6 +1328,9 @@ public class ScanManager {
                    Log.d(TAG, "stop gattClientScanNative");
                }
                mNativeInterface.gattClientScan(false);
                if (!AppScanStats.recordScanRadioStop()) {
                    Log.w(TAG, "There is no scan radio to stop");
                }
            }
            removeScanFilters(client.scannerId);
        }
@@ -1347,6 +1363,9 @@ public class ScanManager {
                    Log.d(TAG, "stop gattClientScanNative");
                }
                mNativeInterface.gattClientScan(false);
                if (!AppScanStats.recordScanRadioStop()) {
                    Log.w(TAG, "There is no scan radio to stop");
                }
            }
        }

+188 −0
Original line number Diff line number Diff line
@@ -29,11 +29,18 @@ import static android.bluetooth.le.ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
@@ -53,6 +60,7 @@ import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.BluetoothAdapterProxy;
import com.android.bluetooth.btservice.MetricsLogger;

import java.util.ArrayList;
import java.util.List;
@@ -65,7 +73,10 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

/**
@@ -98,6 +109,9 @@ public class ScanManagerTest {
    @Mock private GattObjectsFactory mFactory;
    @Mock private GattNativeInterface mNativeInterface;
    @Mock private ScanNativeInterface mScanNativeInterface;
    @Mock private MetricsLogger  mMetricsLogger;

    @Captor ArgumentCaptor<Long> mScanDurationCaptor;

    @Before
    public void setUp() throws Exception {
@@ -130,6 +144,8 @@ public class ScanManagerTest {
        // Mock JNI callback in ScanNativeInterface
        doReturn(true).when(mScanNativeInterface).waitForCallback(anyInt());

        MetricsLogger.setInstanceForTesting(mMetricsLogger);

        TestUtils.startService(mServiceRule, GattService.class);
        mService = GattService.getGattService();
        assertThat(mService).isNotNull();
@@ -158,6 +174,8 @@ public class ScanManagerTest {
        TestUtils.clearAdapterService(mAdapterService);
        BluetoothAdapterProxy.setInstanceForTesting(null);
        GattObjectsFactory.setInstanceForTesting(null);
        MetricsLogger.setInstanceForTesting(null);
        MetricsLogger.getInstance();
    }

    private void testSleep(long millis) {
@@ -1028,4 +1046,174 @@ public class ScanManagerTest {
            assertThat(mScanManager.getSuspendedScanQueue().contains(client)).isFalse();
        }
    }

    @Test
    public void testMetricsScanRadioDurationScreenOn() {
        // Set filtered scan flag
        final boolean isFiltered = true;
        // Turn on screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(true));
        verify(mMetricsLogger, never()).cacheCount(anyInt(), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        // Create scan client
        ScanClient client = createScanClient(0, isFiltered, SCAN_MODE_LOW_POWER);
        // Start scan
        sendMessageWaitForProcessed(createStartStopScanMessage(true, client));
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Stop scan
        sendMessageWaitForProcessed(createStartStopScanMessage(false, client));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
    }

    @Test
    public void testMetricsScanRadioDurationScreenOnOff() {
        // Set filtered scan flag
        final boolean isFiltered = true;
        // Turn on screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(true));
        verify(mMetricsLogger, never()).cacheCount(anyInt(), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        // Create scan client
        ScanClient client = createScanClient(0, isFiltered, SCAN_MODE_LOW_POWER);
        // Start scan
        sendMessageWaitForProcessed(createStartStopScanMessage(true, client));
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Turn off screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(false));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Turn on screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(true));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Stop scan
        sendMessageWaitForProcessed(createStartStopScanMessage(false, client));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
    }

    @Test
    public void testMetricsScanRadioDurationMultiScan() {
        // Set filtered scan flag
        final boolean isFiltered = true;
        // Turn on screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(true));
        verify(mMetricsLogger, never()).cacheCount(anyInt(), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        // Create scan clients with different duty cycles
        ScanClient client = createScanClient(0, isFiltered, SCAN_MODE_LOW_POWER);
        ScanClient client2 = createScanClient(1, isFiltered, SCAN_MODE_BALANCED);
        // Start scan with lower duty cycle
        sendMessageWaitForProcessed(createStartStopScanMessage(true, client));
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atMost(1)).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Start scan with higher duty cycle
        sendMessageWaitForProcessed(createStartStopScanMessage(true, client2));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        testSleep(50);
        // Stop scan with lower duty cycle
        sendMessageWaitForProcessed(createStartStopScanMessage(false, client));
        verify(mMetricsLogger, never()).cacheCount(anyInt(), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
        // Stop scan with higher duty cycle
        sendMessageWaitForProcessed(createStartStopScanMessage(false, client2));
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR), anyLong());
        verify(mMetricsLogger, atLeastOnce()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON), anyLong());
        verify(mMetricsLogger, never()).cacheCount(
                eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF), anyLong());
        Mockito.clearInvocations(mMetricsLogger);
    }

    @Test
    public void testMetricsScanRadioWeightedDuration() {
        // Set filtered scan flag
        final boolean isFiltered = true;
        final long scanTestDuration = 100;
        // Set scan mode map {scan mode (ScanMode) : scan weight (ScanWeight)}
        SparseIntArray scanModeMap = new SparseIntArray();
        scanModeMap.put(SCAN_MODE_SCREEN_OFF, AppScanStats.SCREEN_OFF_LOW_POWER_WEIGHT);
        scanModeMap.put(SCAN_MODE_LOW_POWER, AppScanStats.LOW_POWER_WEIGHT);
        scanModeMap.put(SCAN_MODE_BALANCED, AppScanStats.BALANCED_WEIGHT);
        scanModeMap.put(SCAN_MODE_LOW_LATENCY, AppScanStats.LOW_LATENCY_WEIGHT);
        scanModeMap.put(SCAN_MODE_AMBIENT_DISCOVERY, AppScanStats.AMBIENT_DISCOVERY_WEIGHT);

        // Turn on screen
        sendMessageWaitForProcessed(createScreenOnOffMessage(true));
        Mockito.clearInvocations(mMetricsLogger);
        for (int i = 0; i < scanModeMap.size(); i++) {
            int ScanMode = scanModeMap.keyAt(i);
            long weightedScanDuration = (long)(scanTestDuration * scanModeMap.get(ScanMode) * 0.01);
            Log.d(TAG, "ScanMode: " + String.valueOf(ScanMode)
                    + " weightedScanDuration: " + String.valueOf(weightedScanDuration));

            // Create scan client
            ScanClient client = createScanClient(i, isFiltered, ScanMode);
            // Start scan
            sendMessageWaitForProcessed(createStartStopScanMessage(true, client));
            Mockito.clearInvocations(mMetricsLogger);
            // Wait for scan test duration
            testSleep(scanTestDuration);
            // Stop scan
            sendMessageWaitForProcessed(createStartStopScanMessage(false, client));
            verify(mMetricsLogger, atLeastOnce()).cacheCount(
                    eq(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR),
                    mScanDurationCaptor.capture());
            long capturedDuration = mScanDurationCaptor.getValue();
            Log.d(TAG, "capturedDuration: " + String.valueOf(capturedDuration));
            assertThat(weightedScanDuration <= capturedDuration
                    && capturedDuration <= weightedScanDuration + DELAY_ASYNC_MS).isTrue();
            Mockito.clearInvocations(mMetricsLogger);
        }
    }
}