Loading android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +57 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ final class RemoteDevices { filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.APPLE); mAdapterService.registerReceiver(mReceiver, filter); } Loading Loading @@ -625,6 +627,9 @@ final class RemoteDevices { case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: batteryPercent = getBatteryLevelFromXEventVsc(args); break; case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV: batteryPercent = getBatteryLevelFromAppleBatteryVsc(args); break; } if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { updateBatteryLevel(device, batteryPercent); Loading @@ -633,6 +638,58 @@ final class RemoteDevices { } } /** * Parse * AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue] * vendor specific event * @param args Array of arguments on the right side of assignment * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} * when there is an error parsing the arguments */ @VisibleForTesting static int getBatteryLevelFromAppleBatteryVsc(Object[] args) { if (args.length == 0) { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int numKvPair; if (args[0] instanceof Integer) { numKvPair = (Integer) args[0]; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (args.length != (numKvPair * 2 + 1)) { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int indicatorType; int indicatorValue = -1; for (int i = 0; i < numKvPair; ++i) { Object indicatorTypeObj = args[2 * i + 1]; if (indicatorTypeObj instanceof Integer) { indicatorType = (Integer) indicatorTypeObj; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (indicatorType != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) { continue; } Object indicatorValueObj = args[2 * i + 2]; if (indicatorValueObj instanceof Integer) { indicatorValue = (Integer) indicatorValueObj; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } break; } return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN : (indicatorValue + 1) * 10; } /** * Parse * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] Loading android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +45 −13 Original line number Diff line number Diff line Loading @@ -211,6 +211,12 @@ final class HeadsetStateMachine extends StateMachine { VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID, BluetoothAssignedNumbers.GOOGLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, BluetoothAssignedNumbers.APPLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV, BluetoothAssignedNumbers.APPLE); } private HeadsetStateMachine(HeadsetService context) { Loading Loading @@ -2935,36 +2941,61 @@ final class HeadsetStateMachine extends StateMachine { } /** * @return {@code true} if the given string is a valid vendor-specific AT command. * Process vendor specific AT commands * @param atString AT command after the "AT+" prefix * @param device Remote device that has sent this command */ private boolean processVendorSpecificAt(String atString) { private void processVendorSpecificAt(String atString, BluetoothDevice device) { log("processVendorSpecificAt - atString = " + atString); // Currently we accept only SET type commands. int indexOfEqual = atString.indexOf("="); if (indexOfEqual == -1) { Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } String command = atString.substring(0, indexOfEqual); Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command); if (companyId == null) { Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } String arg = atString.substring(indexOfEqual + 1); if (arg.startsWith("?")) { Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } Object[] args = generateArgs(arg); if (command.equals(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL)) { processAtXapl(args, device); } broadcastVendorSpecificEventIntent( command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, args, mCurrentDevice); atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(mCurrentDevice)); return true; command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, args, device); atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device)); } /** * Process AT+XAPL AT command * @param args command arguments after the equal sign * @param device Remote device that has sent this command */ private void processAtXapl(Object[] args, BluetoothDevice device) { if (args.length != 2) { Log.w(TAG, "processAtXapl() args length must be 2: " + String.valueOf(args.length)); return; } if (!(args[0] instanceof String) || !(args[1] instanceof Integer)) { Log.w(TAG, "processAtXapl() argument types not match"); return; } // feature = 2 indicates that we support battery level reporting only atResponseStringNative("+XAPL=iPhone," + String.valueOf(2), getByteAddress(device)); } private void processUnknownAt(String atString, BluetoothDevice device) { Loading @@ -2977,14 +3008,15 @@ final class HeadsetStateMachine extends StateMachine { log("processUnknownAt - atString = " + atString); String atCommand = parseUnknownAt(atString); int commandType = getAtCommandType(atCommand); if (atCommand.startsWith("+CSCS")) if (atCommand.startsWith("+CSCS")) { processAtCscs(atCommand.substring(5), commandType, device); else if (atCommand.startsWith("+CPBS")) } else if (atCommand.startsWith("+CPBS")) { processAtCpbs(atCommand.substring(5), commandType, device); else if (atCommand.startsWith("+CPBR")) } else if (atCommand.startsWith("+CPBR")) { processAtCpbr(atCommand.substring(5), commandType, device); else if (!processVendorSpecificAt(atCommand)) atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); } else { processVendorSpecificAt(atCommand, device); } } private void processKeyPressed(BluetoothDevice device) { Loading android/app/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -260,6 +260,24 @@ public class RemoteDevicesTest { Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testOnVendorSpecificHeadsetEvent_testCorrectAppleBatteryVsc() { // 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_IPHONEACCEV, BluetoothAssignedNumbers.APPLE, BluetoothHeadset.AT_CMD_TYPE_SET, new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3, 10}, mDevice1)); verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture()); verfyBatteryLevelChangedIntent(mDevice1, 60, mIntentArgument); Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testGetBatteryLevelFromXEventVsc() { Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))); Loading @@ -272,6 +290,43 @@ public class RemoteDevicesTest { RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))); } @Test public void testGetBatteryLevelFromAppleBatteryVsc() { Assert.assertEquals(10, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 0})); Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 9})); Assert.assertEquals(60, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3, 10})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 10})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, -1})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, "5"})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, 35, 37})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc( new Object[] {1, "WRONG", "WRONG"})); } private static void verfyBatteryLevelChangedIntent( BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) { Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, Loading Loading
android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +57 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ final class RemoteDevices { filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS); filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.APPLE); mAdapterService.registerReceiver(mReceiver, filter); } Loading Loading @@ -625,6 +627,9 @@ final class RemoteDevices { case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: batteryPercent = getBatteryLevelFromXEventVsc(args); break; case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV: batteryPercent = getBatteryLevelFromAppleBatteryVsc(args); break; } if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { updateBatteryLevel(device, batteryPercent); Loading @@ -633,6 +638,58 @@ final class RemoteDevices { } } /** * Parse * AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue] * vendor specific event * @param args Array of arguments on the right side of assignment * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} * when there is an error parsing the arguments */ @VisibleForTesting static int getBatteryLevelFromAppleBatteryVsc(Object[] args) { if (args.length == 0) { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int numKvPair; if (args[0] instanceof Integer) { numKvPair = (Integer) args[0]; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (args.length != (numKvPair * 2 + 1)) { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } int indicatorType; int indicatorValue = -1; for (int i = 0; i < numKvPair; ++i) { Object indicatorTypeObj = args[2 * i + 1]; if (indicatorTypeObj instanceof Integer) { indicatorType = (Integer) indicatorTypeObj; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } if (indicatorType != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) { continue; } Object indicatorValueObj = args[2 * i + 2]; if (indicatorValueObj instanceof Integer) { indicatorValue = (Integer) indicatorValueObj; } else { Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value"); return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; } break; } return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN : (indicatorValue + 1) * 10; } /** * Parse * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] Loading
android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +45 −13 Original line number Diff line number Diff line Loading @@ -211,6 +211,12 @@ final class HeadsetStateMachine extends StateMachine { VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID, BluetoothAssignedNumbers.GOOGLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, BluetoothAssignedNumbers.APPLE); VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV, BluetoothAssignedNumbers.APPLE); } private HeadsetStateMachine(HeadsetService context) { Loading Loading @@ -2935,36 +2941,61 @@ final class HeadsetStateMachine extends StateMachine { } /** * @return {@code true} if the given string is a valid vendor-specific AT command. * Process vendor specific AT commands * @param atString AT command after the "AT+" prefix * @param device Remote device that has sent this command */ private boolean processVendorSpecificAt(String atString) { private void processVendorSpecificAt(String atString, BluetoothDevice device) { log("processVendorSpecificAt - atString = " + atString); // Currently we accept only SET type commands. int indexOfEqual = atString.indexOf("="); if (indexOfEqual == -1) { Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } String command = atString.substring(0, indexOfEqual); Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command); if (companyId == null) { Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } String arg = atString.substring(indexOfEqual + 1); if (arg.startsWith("?")) { Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); return false; atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); return; } Object[] args = generateArgs(arg); if (command.equals(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL)) { processAtXapl(args, device); } broadcastVendorSpecificEventIntent( command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, args, mCurrentDevice); atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(mCurrentDevice)); return true; command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, args, device); atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device)); } /** * Process AT+XAPL AT command * @param args command arguments after the equal sign * @param device Remote device that has sent this command */ private void processAtXapl(Object[] args, BluetoothDevice device) { if (args.length != 2) { Log.w(TAG, "processAtXapl() args length must be 2: " + String.valueOf(args.length)); return; } if (!(args[0] instanceof String) || !(args[1] instanceof Integer)) { Log.w(TAG, "processAtXapl() argument types not match"); return; } // feature = 2 indicates that we support battery level reporting only atResponseStringNative("+XAPL=iPhone," + String.valueOf(2), getByteAddress(device)); } private void processUnknownAt(String atString, BluetoothDevice device) { Loading @@ -2977,14 +3008,15 @@ final class HeadsetStateMachine extends StateMachine { log("processUnknownAt - atString = " + atString); String atCommand = parseUnknownAt(atString); int commandType = getAtCommandType(atCommand); if (atCommand.startsWith("+CSCS")) if (atCommand.startsWith("+CSCS")) { processAtCscs(atCommand.substring(5), commandType, device); else if (atCommand.startsWith("+CPBS")) } else if (atCommand.startsWith("+CPBS")) { processAtCpbs(atCommand.substring(5), commandType, device); else if (atCommand.startsWith("+CPBR")) } else if (atCommand.startsWith("+CPBR")) { processAtCpbr(atCommand.substring(5), commandType, device); else if (!processVendorSpecificAt(atCommand)) atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device)); } else { processVendorSpecificAt(atCommand, device); } } private void processKeyPressed(BluetoothDevice device) { Loading
android/app/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -260,6 +260,24 @@ public class RemoteDevicesTest { Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testOnVendorSpecificHeadsetEvent_testCorrectAppleBatteryVsc() { // 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_IPHONEACCEV, BluetoothAssignedNumbers.APPLE, BluetoothHeadset.AT_CMD_TYPE_SET, new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3, 10}, mDevice1)); verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture()); verfyBatteryLevelChangedIntent(mDevice1, 60, mIntentArgument); Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue()); } @Test public void testGetBatteryLevelFromXEventVsc() { Assert.assertEquals(37, RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(3, 8))); Loading @@ -272,6 +290,43 @@ public class RemoteDevicesTest { RemoteDevices.getBatteryLevelFromXEventVsc(getXEventArray(-1, -1))); } @Test public void testGetBatteryLevelFromAppleBatteryVsc() { Assert.assertEquals(10, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 0})); Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 9})); Assert.assertEquals(60, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3, 10})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5, 2, 1, 3})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 10})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, -1})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, "5"})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, 35, 37})); Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, RemoteDevices.getBatteryLevelFromAppleBatteryVsc( new Object[] {1, "WRONG", "WRONG"})); } private static void verfyBatteryLevelChangedIntent( BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) { Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, Loading