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

Commit 59b2a6cc authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add APIs to get remote device's battery level (2/2)"

parents a01bf028 0b7b3963
Loading
Loading
Loading
Loading
+18 −0
Original line number Original line Diff line number Diff line
@@ -1281,6 +1281,17 @@ public class AdapterService extends Service {
            return service.sdpSearch(device,uuid);
            return service.sdpSearch(device,uuid);
        }
        }


        public int getBatteryLevel(BluetoothDevice device) {
            if (!Utils.checkCaller()) {
                Log.w(TAG, "getBatteryLevel(): not allowed for non-active user");
                return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
            }

            AdapterService service = getService();
            if (service == null) return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
            return service.getBatteryLevel(device);
        }

        public boolean factoryReset() {
        public boolean factoryReset() {
            AdapterService service = getService();
            AdapterService service = getService();
            if (service == null) return false;
            if (service == null) return false;
@@ -1695,6 +1706,13 @@ public class AdapterService extends Service {
        return true;
        return true;
    }
    }


    int getBatteryLevel(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
        if (deviceProp == null) return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
        return deviceProp.getBatteryLevel();
    }

    boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
    boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+83 −10
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Handler;
import android.os.Handler;
import android.os.Message;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.ParcelUuid;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Log;
import com.android.bluetooth.R;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.Utils;
@@ -41,7 +42,7 @@ final class RemoteDevices {
    private static BluetoothAdapter mAdapter;
    private static BluetoothAdapter mAdapter;
    private static AdapterService mAdapterService;
    private static AdapterService mAdapterService;
    private static ArrayList<BluetoothDevice> mSdpTracker;
    private static ArrayList<BluetoothDevice> mSdpTracker;
    private Object mObject = new Object();
    private final Object mObject = new Object();


    private static final int UUID_INTENT_DELAY = 6000;
    private static final int UUID_INTENT_DELAY = 6000;
    private static final int MESSAGE_UUID_INTENT = 1;
    private static final int MESSAGE_UUID_INTENT = 1;
@@ -121,6 +122,7 @@ final class RemoteDevices {
        private int mBondState;
        private int mBondState;
        private BluetoothDevice mDevice;
        private BluetoothDevice mDevice;
        private boolean isBondingInitiatedLocally;
        private boolean isBondingInitiatedLocally;
        private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;


        DeviceProperties() {
        DeviceProperties() {
            mBondState = BluetoothDevice.BOND_NONE;
            mBondState = BluetoothDevice.BOND_NONE;
@@ -256,6 +258,21 @@ final class RemoteDevices {
                return isBondingInitiatedLocally;
                return isBondingInitiatedLocally;
            }
            }
        }
        }

        int getBatteryLevel() {
            synchronized (mObject) {
                return mBatteryLevel;
            }
        }

        /**
         * @param batteryLevel the mBatteryLevel to set
         */
        void setBatteryLevel(int batteryLevel) {
            synchronized (mObject) {
                this.mBatteryLevel = batteryLevel;
            }
        }
    }
    }


    private void sendUuidIntent(BluetoothDevice device) {
    private void sendUuidIntent(BluetoothDevice device) {
@@ -270,8 +287,9 @@ final class RemoteDevices {
    }
    }


    /**
    /**
   * When bonding is initiated to remote device that we have never seen, i.e Out Of Band pairing, we
     * When bonding is initiated to remote device that we have never seen, i.e Out Of Band pairing,
   * must add device first before setting it's properties. This is a helper method for doing that.
     * we must add device first before setting it's properties. This is a helper method for doing
     * that.
     */
     */
    void setBondingInitiatedLocally(byte[] address) {
    void setBondingInitiatedLocally(byte[] address) {
        DeviceProperties properties;
        DeviceProperties properties;
@@ -286,6 +304,57 @@ final class RemoteDevices {
        properties.setBondingInitiatedLocally(true);
        properties.setBondingInitiatedLocally(true);
    }
    }


    /**
     * Update battery level in device properties
     * @param device The remote device to be updated
     * @param batteryLevel Battery level Indicator between 0-100,
     *                    {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} is error
     */
    @VisibleForTesting
    void updateBatteryLevel(BluetoothDevice device, int batteryLevel) {
        if (device == null || batteryLevel < 0 || batteryLevel > 100) {
            warnLog("Invalid parameters device=" + String.valueOf(device == null)
                    + ", batteryLevel=" + String.valueOf(batteryLevel));
            return;
        }
        DeviceProperties deviceProperties = getDeviceProperties(device);
        if (deviceProperties == null) {
            deviceProperties = addDeviceProperties(Utils.getByteAddress(device));
        }
        synchronized (mObject) {
            int currentBatteryLevel = deviceProperties.getBatteryLevel();
            if (batteryLevel == currentBatteryLevel) {
                debugLog("Same battery level for device " + device + " received "
                        + String.valueOf(batteryLevel) + "%");
                return;
            }
            deviceProperties.setBatteryLevel(batteryLevel);
        }
        Intent intent = new Intent(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        mAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
        Log.d(TAG, "Updated device " + device + " battery level to " + String.valueOf(batteryLevel)
                        + "%");
    }

    /**
     * Reset battery level property to {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} for a device
     * @param device device whose battery level property needs to be reset
     */
    @VisibleForTesting
    void resetBatteryLevel(BluetoothDevice device) {
        if (device == null) {
            warnLog("device is null");
            return;
        }
        DeviceProperties deviceProperties = getDeviceProperties(device);
        if (deviceProperties == null) {
            return;
        }
        deviceProperties.setBatteryLevel(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
    }


    void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {
    void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {
        Intent intent;
        Intent intent;
@@ -419,6 +488,10 @@ final class RemoteDevices {
            } else if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
            } else if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
                intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED);
                intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED);
            }
            }
            // Reset battery level on complete disconnection
            if (mAdapterService.getConnectionState(device) == 0) {
                resetBatteryLevel(device);
            }
            debugLog("aclStateChangeCallback: Adapter State: "
            debugLog("aclStateChangeCallback: Adapter State: "
                    + BluetoothAdapter.nameForState(state) + " Disconnected: " + device);
                    + BluetoothAdapter.nameForState(state) + " Disconnected: " + device);
        }
        }
@@ -459,19 +532,19 @@ final class RemoteDevices {
        }
        }
    };
    };


    private void errorLog(String msg) {
    private static void errorLog(String msg) {
        Log.e(TAG, msg);
        Log.e(TAG, msg);
    }
    }


    private void debugLog(String msg) {
    private static void debugLog(String msg) {
        if (DBG) Log.d(TAG, msg);
        if (DBG) Log.d(TAG, msg);
    }
    }


    private void infoLog(String msg) {
    private static void infoLog(String msg) {
        if (DBG) Log.i(TAG, msg);
        if (DBG) Log.i(TAG, msg);
    }
    }


    private void warnLog(String msg) {
    private static void warnLog(String msg) {
        Log.w(TAG, msg);
        Log.w(TAG, msg);
    }
    }


+208 −26
Original line number Original line Diff line number Diff line
package com.android.bluetooth.btservice;
package com.android.bluetooth.btservice;


import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.support.test.runner.AndroidJUnit4;
import android.os.ParcelUuid;

import android.util.Log;
import com.android.bluetooth.Utils;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.lang.Thread;


import android.test.AndroidTestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;


import static org.mockito.Mockito.mock;
@RunWith(AndroidJUnit4.class)
import static org.mockito.Mockito.any;
public class RemoteDevicesTest {
import static org.mockito.Mockito.anyString;
    private static final String TEST_BT_ADDR_1 = "00:11:22:33:44:55";
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;


public class RemoteDevicesTest extends AndroidTestCase {
    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
    public void testSendUuidIntent() {
    private ArgumentCaptor<String> mStringArgument = ArgumentCaptor.forClass(String.class);
        if (Looper.myLooper() == null) Looper.prepare();
    private BluetoothDevice mDevice1;
    private RemoteDevices mRemoteDevices;


        AdapterService mockService = mock(AdapterService.class);
    @Mock private AdapterService mAdapterService;
        RemoteDevices devices = new RemoteDevices(mockService);

        BluetoothDevice device =
    @Before
                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
    public void setUp() {
        devices.updateUuids(device);
        MockitoAnnotations.initMocks(this);
        if (Looper.myLooper() == null) Looper.prepare();
        mDevice1 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR_1);
        mRemoteDevices = new RemoteDevices(mAdapterService);
    }


    @Test
    public void testSendUuidIntent() {
        mRemoteDevices.updateUuids(mDevice1);
        Looper.myLooper().quitSafely();
        Looper.myLooper().quitSafely();
        Looper.loop();
        Looper.loop();


        verify(mockService).sendBroadcast(any(), anyString());
        verify(mAdapterService).sendBroadcast(any(), anyString());
        verifyNoMoreInteractions(mockService);
        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testUpdateBatteryLevel_normalSequence() {
        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());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, 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 update same battery level for the same device does not trigger intent
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService).sendBroadcast(any(), anyString());

        // Verify that updating battery level to different value triggers the intent again
        batteryLevel = 15;
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService, times(2))
                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);

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

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testUpdateBatteryLevel_errorNegativeValue() {
        int batteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;

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

        // Verify that updating with invalid battery level does not trigger the intent
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService, never()).sendBroadcast(any(), anyString());

        // Verify that device property stays null after invalid update
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testUpdateBatteryLevel_errorTooLargeValue() {
        int batteryLevel = 101;

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

        // Verify that updating invalid battery level does not trigger the intent
        mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
        verify(mAdapterService, never()).sendBroadcast(any(), anyString());

        // Verify that device property stays null after invalid update
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testResetBatteryLevel_testResetBeforeUpdate() {
        // Verify that device property is null initially
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        // Verify that resetting battery level keeps device property null
        mRemoteDevices.resetBatteryLevel(mDevice1);
        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testResetBatteryLevel_testResetAfterUpdate() {
        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());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, 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.resetBatteryLevel(mDevice1);
        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(2))
                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());

        verifyNoMoreInteractions(mAdapterService);
    }

    @Test
    public void testResetBatteryLevel_testAclStateChangeCallback() {
        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());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());

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

        // Verify that when device is completely disconnected, RemoteDevices reset battery level to
        // BluetoothDevice.BATTERY_LEVEL_UNKNOWN
        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
        mRemoteDevices.aclStateChangeCallback(
                0, Utils.getByteAddress(mDevice1), AbstractionLayer.BT_ACL_STATE_DISCONNECTED);
        verify(mAdapterService).getState();
        verify(mAdapterService).getConnectionState(mDevice1);
        verify(mAdapterService, times(2)).sendBroadcast(any(), anyString());
        Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
        Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel());

        // 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());
        verfyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());

        verifyNoMoreInteractions(mAdapterService);
    }

    private static void verfyBatteryLevelChangedIntent(
            BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) {
        Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED,
                intentArgument.getValue().getAction());
        Assert.assertEquals(
                device, intentArgument.getValue().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
        Assert.assertEquals(batteryLevel,
                intentArgument.getValue().getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
        Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT,
                intentArgument.getValue().getFlags());
    }
    }
}
}