Loading android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +95 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.btservice; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -65,6 +66,9 @@ final class RemoteDevices { case BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED: onHfIndicatorValueChanged(intent); break; case BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT: onVendorSpecificHeadsetEvent(intent); break; default: Log.w(TAG, "Unhandled intent: " + intent); break; Loading @@ -86,6 +90,9 @@ final class RemoteDevices { void init() { IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS); mAdapterService.registerReceiver(mReceiver, filter); } Loading Loading @@ -575,6 +582,94 @@ final class RemoteDevices { } } /** * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent */ @VisibleForTesting void onVendorSpecificHeadsetEvent(Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null"); return; } String cmd = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); if (cmd == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null"); return; } int cmdType = intent.getIntExtra( BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, -1); // Only process set command if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) { debugLog("onVendorSpecificHeadsetEvent() only SET command is processed"); return; } Object[] args = (Object[]) intent.getExtras().get( BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); if (args == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null"); return; } int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; switch (cmd) { case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: batteryPercent = getBatteryLevelFromXEventVsc(args); break; } if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { updateBatteryLevel(device, batteryPercent); infoLog("Updated device " + device + " battery level to " + String.valueOf(batteryPercent) + "%"); } } /** * Parse * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] * vendor specific event * @param args Array of arguments on the right side of SET command * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} * when there is an error parsing the arguments */ @VisibleForTesting static int getBatteryLevelFromXEventVsc(Object[] args) { if (args.length == 0) { Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } Object eventNameObj = args[0]; if (!(eventNameObj instanceof String)) { Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } String eventName = (String) eventNameObj; if (!eventName.equals( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) { infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (args.length != 5) { Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: " + String.valueOf(args.length)); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) { Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int batteryLevel = (Integer) args[1]; int numberOfLevels = (Integer) args[2]; if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) { Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel=" + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(numberOfLevels)); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } return batteryLevel * 100 / numberOfLevels; } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { Loading android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +6 −2 Original line number Diff line number Diff line Loading @@ -205,8 +205,12 @@ final class HeadsetStateMachine extends StateMachine { classInitNative(); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<String, Integer>(); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+XEVENT", BluetoothAssignedNumbers.PLANTRONICS); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+ANDROID", BluetoothAssignedNumbers.GOOGLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT, BluetoothAssignedNumbers.PLANTRONICS); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID, BluetoothAssignedNumbers.GOOGLE); } private HeadsetStateMachine(HeadsetService context) { Loading android/app/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.content.Intent; Loading @@ -26,6 +27,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @RunWith(AndroidJUnit4.class) public class RemoteDevicesTest { private static final String TEST_BT_ADDR_1 = "00:11:22:33:44:55"; Loading Loading @@ -242,6 +245,33 @@ public class RemoteDevicesTest { Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1)); } @Test public void testOnVendorSpecificHeadsetEvent_testCorrectPlantronicsXEvent() { // Verify that device property is null initially Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1)); // Verify that correct ACTION_VENDOR_SPECIFIC_HEADSET_EVENT updates battery level mRemoteDevices.onVendorSpecificHeadsetEvent(getVendorSpecificHeadsetEventIntent( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT, BluetoothAssignedNumbers.PLANTRONICS, BluetoothHeadset.AT_CMD_TYPE_SET, getXEventArray(3, 8), mDevice1)); verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture()); verfyBatteryLevelChangedIntent(mDevice1, 37, mIntentArgument); Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testGetBatteryLevelFromXEventVsc() { Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))); Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))); } private static void verfyBatteryLevelChangedIntent( BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) { Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, Loading @@ -262,4 +292,27 @@ public class RemoteDevicesTest { intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, batteryLevel); return intent; } private static Intent getVendorSpecificHeadsetEventIntent(String command, int companyId, int commandType, Object[] arguments, BluetoothDevice device) { Intent intent = new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command); intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, commandType); // assert: all elements of args are Serializable intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + Integer.toString(companyId)); return intent; } private static Object[] getXEventArray(int batteryLevel, int numLevels) { ArrayList<Object> list = new ArrayList<>(); list.add(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL); list.add(batteryLevel); list.add(numLevels); list.add(0); list.add(0); return list.toArray(); } } Loading
android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +95 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.btservice; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -65,6 +66,9 @@ final class RemoteDevices { case BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED: onHfIndicatorValueChanged(intent); break; case BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT: onVendorSpecificHeadsetEvent(intent); break; default: Log.w(TAG, "Unhandled intent: " + intent); break; Loading @@ -86,6 +90,9 @@ final class RemoteDevices { void init() { IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS); mAdapterService.registerReceiver(mReceiver, filter); } Loading Loading @@ -575,6 +582,94 @@ final class RemoteDevices { } } /** * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent */ @VisibleForTesting void onVendorSpecificHeadsetEvent(Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null"); return; } String cmd = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); if (cmd == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null"); return; } int cmdType = intent.getIntExtra( BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, -1); // Only process set command if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) { debugLog("onVendorSpecificHeadsetEvent() only SET command is processed"); return; } Object[] args = (Object[]) intent.getExtras().get( BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); if (args == null) { Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null"); return; } int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; switch (cmd) { case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: batteryPercent = getBatteryLevelFromXEventVsc(args); break; } if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { updateBatteryLevel(device, batteryPercent); infoLog("Updated device " + device + " battery level to " + String.valueOf(batteryPercent) + "%"); } } /** * Parse * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] * vendor specific event * @param args Array of arguments on the right side of SET command * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} * when there is an error parsing the arguments */ @VisibleForTesting static int getBatteryLevelFromXEventVsc(Object[] args) { if (args.length == 0) { Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } Object eventNameObj = args[0]; if (!(eventNameObj instanceof String)) { Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } String eventName = (String) eventNameObj; if (!eventName.equals( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) { infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (args.length != 5) { Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: " + String.valueOf(args.length)); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) { Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int batteryLevel = (Integer) args[1]; int numberOfLevels = (Integer) args[2]; if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) { Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel=" + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(numberOfLevels)); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } return batteryLevel * 100 / numberOfLevels; } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { Loading
android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +6 −2 Original line number Diff line number Diff line Loading @@ -205,8 +205,12 @@ final class HeadsetStateMachine extends StateMachine { classInitNative(); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<String, Integer>(); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+XEVENT", BluetoothAssignedNumbers.PLANTRONICS); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+ANDROID", BluetoothAssignedNumbers.GOOGLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT, BluetoothAssignedNumbers.PLANTRONICS); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID, BluetoothAssignedNumbers.GOOGLE); } private HeadsetStateMachine(HeadsetService context) { Loading
android/app/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java +53 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.content.Intent; Loading @@ -26,6 +27,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @RunWith(AndroidJUnit4.class) public class RemoteDevicesTest { private static final String TEST_BT_ADDR_1 = "00:11:22:33:44:55"; Loading Loading @@ -242,6 +245,33 @@ public class RemoteDevicesTest { Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1)); } @Test public void testOnVendorSpecificHeadsetEvent_testCorrectPlantronicsXEvent() { // Verify that device property is null initially Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1)); // Verify that correct ACTION_VENDOR_SPECIFIC_HEADSET_EVENT updates battery level mRemoteDevices.onVendorSpecificHeadsetEvent(getVendorSpecificHeadsetEventIntent( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT, BluetoothAssignedNumbers.PLANTRONICS, BluetoothHeadset.AT_CMD_TYPE_SET, getXEventArray(3, 8), mDevice1)); verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture()); verfyBatteryLevelChangedIntent(mDevice1, 37, mIntentArgument); Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testGetBatteryLevelFromXEventVsc() { Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))); Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(1, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, 1))); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))); } private static void verfyBatteryLevelChangedIntent( BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) { Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, Loading @@ -262,4 +292,27 @@ public class RemoteDevicesTest { intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, batteryLevel); return intent; } private static Intent getVendorSpecificHeadsetEventIntent(String command, int companyId, int commandType, Object[] arguments, BluetoothDevice device) { Intent intent = new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command); intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, commandType); // assert: all elements of args are Serializable intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + Integer.toString(companyId)); return intent; } private static Object[] getXEventArray(int batteryLevel, int numLevels) { ArrayList<Object> list = new ArrayList<>(); list.add(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL); list.add(batteryLevel); list.add(numLevels); list.add(0); list.add(0); return list.toArray(); } }