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

Commit e3b77cc8 authored by Sal Savage's avatar Sal Savage
Browse files

Let HFP AG report battery level through BluetoothDevice#getBatteryLevel

Tag: #feature
Bug: 218377110
Test: atest BluetoothInstrumentationTests
Change-Id: I5760fe44e503428818de3a9399fa3365f24868c1
parent fd34435a
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothConnectionCallback;
import android.content.BroadcastReceiver;
@@ -110,6 +111,12 @@ final class RemoteDevices {
                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                    onHeadsetConnectionStateChanged(intent);
                    break;
                case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED:
                    onHeadsetClientConnectionStateChanged(intent);
                    break;
                case BluetoothHeadsetClient.ACTION_AG_EVENT:
                    onAgIndicatorValueChanged(intent);
                    break;
                default:
                    Log.w(TAG, "Unhandled intent: " + intent);
                    break;
@@ -157,6 +164,8 @@ final class RemoteDevices {
        filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
                + BluetoothAssignedNumbers.APPLE);
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
        sAdapterService.registerReceiver(mReceiver, filter);
    }

@@ -1038,6 +1047,38 @@ final class RemoteDevices {
        return batteryLevel * 100 / (numberOfLevels - 1);
    }

    /**
     * Handles headset client connection state change event
     * @param intent must be {@link BluetoothHeadsetClient#ACTION_CONNECTION_STATE_CHANGED} intent
     */
    @VisibleForTesting
    void onHeadsetClientConnectionStateChanged(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (device == null) {
            Log.e(TAG, "onHeadsetClientConnectionStateChanged() remote device is null");
            return;
        }
        if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED)
                == BluetoothProfile.STATE_DISCONNECTED) {
            // TODO: Rework this when non-HFP sources of battery level indication is added
            resetBatteryLevel(device);
        }
    }

    @VisibleForTesting
    void onAgIndicatorValueChanged(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (device == null) {
            Log.e(TAG, "onAgIndicatorValueChanged() remote device is null");
            return;
        }

        if (intent.hasExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL)) {
            int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, -1);
            updateBatteryLevel(device, batteryLevel);
        }
    }

    private static void errorLog(String msg) {
        Log.e(TAG, msg);
    }
+113 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.os.Bundle;
@@ -437,6 +438,84 @@ public class RemoteDevicesTest {
                        new Object[]{1, "WRONG", "WRONG"}));
    }

    @Test
    public void testResetBatteryLevelOnHeadsetClientStateChange() {
        int batteryLevel = 10;

        // Verify that device property is null initially
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
                any(Bundle.class));
        verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());

        // Verify that user can get battery level after the update
        Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
                batteryLevel);

        // Verify that resetting battery level changes it back to BluetoothDevice
        // .BATTERY_LEVEL_UNKNOWN
        mRemoteDevices.onHeadsetClientConnectionStateChanged(
                getHeadsetClientConnectionStateChangedIntent(mDevice1,
                        BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_DISCONNECTED));

        // Verify BATTERY_LEVEL_CHANGED intent is sent after first reset
        verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
                mStringArgument.capture(), any(Bundle.class));
        verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                mIntentArgument);
        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());

        // Verify value is reset in properties
        Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
                BluetoothDevice.BATTERY_LEVEL_UNKNOWN);

        // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
                mStringArgument.capture(), any(Bundle.class));
        verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testAGIndicatorParser_testCorrectValue() {
        int batteryLevel = 10;

        // Verify that device property is null initially
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        // Verify that ACTION_AG_EVENT intent updates battery level
        mRemoteDevices.onAgIndicatorValueChanged(
                getAgIndicatorIntent(mDevice1, null, null, null, new Integer(batteryLevel), null));
        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
                any(Bundle.class));
        verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
    }

    @Test
    public void testAgIndicatorParser_testWrongIndicatorId() {
        int batteryLevel = 10;

        // Verify that device property is null initially
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        // Verify that ACTION_AG_EVENT intent updates battery level
        mRemoteDevices.onAgIndicatorValueChanged(
                getAgIndicatorIntent(mDevice1, new Integer(1), null, null, null, null));
        verify(mAdapterService, never()).sendBroadcast(any(), anyString());
        // Verify that device property is still null after invalid update
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
    }

    private static void verifyBatteryLevelChangedIntent(BluetoothDevice device, int batteryLevel,
            ArgumentCaptor<Intent> intentArgument) {
        verifyBatteryLevelChangedIntent(device, batteryLevel, intentArgument.getValue());
@@ -492,4 +571,38 @@ public class RemoteDevicesTest {
        list.add(0);
        return list.toArray();
    }

    private static Intent getHeadsetClientConnectionStateChangedIntent(BluetoothDevice device,
            int oldState, int newState) {
        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
        return intent;
    }

    private static Intent getAgIndicatorIntent(BluetoothDevice device, Integer networkStatus,
            Integer networkStrength, Integer roaming, Integer batteryLevel, String operatorName) {
        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);

        if (networkStatus != null) {
            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS, networkStatus.intValue());
        }
        if (networkStrength != null) {
            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
                    networkStrength.intValue());
        }
        if (roaming != null) {
            intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING, roaming.intValue());
        }
        if (batteryLevel != null) {
            intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, batteryLevel.intValue());
        }
        if (operatorName != null) {
            intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME, operatorName);
        }

        return intent;
    }
}