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

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

Merge "Refine AT command of BluetoothSinkAudioPolicy"

parents b36f3227 145b3827
Loading
Loading
Loading
Loading
+45 −40
Original line number Diff line number Diff line
@@ -159,8 +159,6 @@ public class HeadsetStateMachine extends StateMachine {
    // Audio disconnect timeout retry count
    private int mAudioDisconnectRetry = 0;

    static final int HFP_SET_AUDIO_POLICY = 1;

    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;

    // Keys are AT commands, and values are the company IDs.
@@ -1966,87 +1964,90 @@ public class HeadsetStateMachine extends StateMachine {
    }

    /**
     * Process Android specific AT commands.
     * Look for Android specific AT command starts with AT+ANDROID and try to process it
     *
     * @param atString AT command after the "AT+" prefix. Starts with "ANDROID"
     * @param atString AT command in string
     * @param device Remote device that has sent this command
     * @return true if the command is processed, false if not.
     */
    private void processAndroidAt(String atString, BluetoothDevice device) {
        log("processAndroidSpecificAt - atString = " + atString);
    @VisibleForTesting
    boolean checkAndProcessAndroidAt(String atString, BluetoothDevice device) {
        log("checkAndProcessAndroidAt - atString = " + atString);

        if (atString.equals("+ANDROID=?")) {
            // feature request type command
            processAndroidAtFeatureRequest(device);
            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
            return true;
        } else if (atString.startsWith("+ANDROID=")) {
            // set type command
            int equalIndex = atString.indexOf("=");
            String arg = atString.substring(equalIndex + 1);

            if (arg.isEmpty()) {
                Log.e(TAG, "Command Invalid!");
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                return;
                Log.w(TAG, "Android AT command is empty");
                return false;
            }

            Object[] args = generateArgs(arg);

            if (!(args[0] instanceof Integer)) {
                Log.e(TAG, "Type ID is invalid");
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                return;
            }

            int type = (Integer) args[0];
            String type = (String) args[0];

            if (type == HFP_SET_AUDIO_POLICY) {
                processAndroidAtSetAudioPolicy(args, device);
            if (type.equals(BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID)) {
                Log.d(TAG, "Processing command: " + atString);
                if (processAndroidAtSinkAudioPolicy(args, device)) {
                    mNativeInterface.atResponseCode(device,
                            HeadsetHalConstants.AT_RESPONSE_OK, 0);
                } else {
                Log.w(TAG, "Undefined AT+ANDROID command");
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                return;
                    Log.w(TAG, "Invalid SinkAudioPolicy parameters!");
                    mNativeInterface.atResponseCode(device,
                            HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                }
                return true;
            } else {
            Log.e(TAG, "Undefined AT+ANDROID command");
            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
            return;
                Log.w(TAG, "Undefined Android command type: " + type);
                return false;
            }
        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        }

        Log.w(TAG, "Unhandled +ANDROID command: " + atString);
        return false;
    }

    private void processAndroidAtFeatureRequest(BluetoothDevice device) {
        /*
            replying with +ANDROID=1
            here, 1 is the feature id for audio policy

            currently we only support one type of feature
            replying with +ANDROID: (<feature1>, <feature2>, ...)
            currently we only support one type of feature: SINKAUDIOPOLICY
        */
        mNativeInterface.atResponseString(device,
                BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID
                + ": " + HFP_SET_AUDIO_POLICY);
                + ": (" + BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID + ")");
    }

    /**
     * Process AT+ANDROID AT command
     * Process AT+ANDROID=SINKAUDIOPOLICY AT command
     *
     * @param args command arguments after the equal sign
     * @param device Remote device that has sent this command
     * @return true on success, false on error
     */
    private void processAndroidAtSetAudioPolicy(Object[] args, BluetoothDevice device) {
    @VisibleForTesting
    boolean processAndroidAtSinkAudioPolicy(Object[] args, BluetoothDevice device) {
        if (args.length != 4) {
            Log.e(TAG, "processAndroidAtSetAudioPolicy() args length must be 4: "
            Log.e(TAG, "processAndroidAtSinkAudioPolicy() args length must be 4: "
                    + String.valueOf(args.length));
            return;
            return false;
        }
        if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)
                || !(args[3] instanceof Integer)) {
            Log.e(TAG, "processAndroidAtSetAudioPolicy() argument types not matched");
            return;
            Log.e(TAG, "processAndroidAtSinkAudioPolicy() argument types not matched");
            return false;
        }

        if (!mDevice.equals(device)) {
            Log.e(TAG, "processAndroidAtSetAudioPolicy(): argument device " + device
            Log.e(TAG, "processAndroidAtSinkAudioPolicy(): argument device " + device
                    + " doesn't match mDevice " + mDevice);
            return;
            return false;
        }

        int callEstablishPolicy = (Integer) args[1];
@@ -2058,6 +2059,7 @@ public class HeadsetStateMachine extends StateMachine {
                .setActiveDevicePolicyAfterConnection(connectingTimePolicy)
                .setInBandRingtonePolicy(inbandPolicy)
                .build());
        return true;
    }

    /**
@@ -2129,6 +2131,9 @@ public class HeadsetStateMachine extends StateMachine {
            processAtCpbs(atCommand.substring(5), commandType, device);
        } else if (atCommand.startsWith("+CPBR")) {
            processAtCpbr(atCommand.substring(5), commandType, device);
        } else if (atCommand.startsWith("+ANDROID")
                && checkAndProcessAndroidAt(atCommand, device)) {
            // Do nothing
        } else {
            processVendorSpecificAt(atCommand, device);
        }
+42 −5
Original line number Diff line number Diff line
@@ -188,8 +188,6 @@ public class HeadsetClientStateMachine extends StateMachine {

    private final boolean mClccPollDuringCall;

    private static final int CALL_AUDIO_POLICY_FEATURE_ID = 1;

    public int mAudioPolicyRemoteSupported;
    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
    private final int mConnectingTimePolicyProperty;
@@ -1217,14 +1215,15 @@ public class HeadsetClientStateMachine extends StateMachine {
                            break;

                        case StackEvent.EVENT_TYPE_UNKNOWN_EVENT:
                            if (mVendorProcessor.processEvent(event.valueString, event.device)) {
                                mQueuedActions.poll();
                            if (mVendorProcessor.isAndroidAtCommand(event.valueString)
                                    && processAndroidSlcCommand(event.valueString, event.device)) {
                                transitionTo(mConnected);
                            } else {
                                Log.e(TAG, "Unknown event :" + event.valueString
                                        + " for device " + event.device);
                            }
                            break;

                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
@@ -2027,6 +2026,44 @@ public class HeadsetClientStateMachine extends StateMachine {
        HfpClientConnectionService.onAudioStateChanged(device, newState, prevState);
    }

    @VisibleForTesting
    boolean processAndroidSlcCommand(String atString, BluetoothDevice device) {
        if (!mCurrentDevice.equals(device) || atString.lastIndexOf("+ANDROID:") < 0) {
            return false;
        }

        // Check if it is +ANDROID: (<feature1>,...),(<feature2>, ...) the reply for AT+ANDROID=?
        while (true) {
            int indexUpperBucket = atString.indexOf("(");
            int indexLowerBucket = atString.indexOf(")");

            if (indexUpperBucket < 0 || indexLowerBucket < 0
                    || indexUpperBucket >= indexLowerBucket) {
                break;
            }
            String feature = atString.substring(indexUpperBucket + 1, indexLowerBucket);
            Log.d(TAG, "processAndroidSlcCommand: feature=[" + feature + "]");
            processAndroidAtFeature(feature.split(","));

            atString = atString.substring(indexLowerBucket + 1);
        }
        return true;
    }

    private void processAndroidAtFeature(String[] args) {
        if (args.length < 1) {
            Log.e(TAG, "processAndroidAtFeature: Invalid feature length");
            return;
        }

        String featureId = args[0];
        if (featureId.equals(BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID)) {
            Log.i(TAG, "processAndroidAtFeature:"
                    + BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID + " supported");
            setAudioPolicyRemoteSupported(true);
        }
    }

    // This method does not check for error condition (newState == prevState)
    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
        logD("Connection state " + device + ": " + prevState + "->" + newState);
@@ -2226,7 +2263,7 @@ public class HeadsetClientStateMachine extends StateMachine {

    private String createMaskString(BluetoothSinkAudioPolicy policies) {
        StringBuilder mask = new StringBuilder();
        mask.append(Integer.toString(CALL_AUDIO_POLICY_FEATURE_ID));
        mask.append(BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID);
        mask.append("," + policies.getCallEstablishPolicy());
        mask.append("," + policies.getActiveDevicePolicyAfterConnection());
        mask.append("," + policies.getInBandRingtonePolicy());
+20 −9
Original line number Diff line number Diff line
@@ -127,12 +127,7 @@ class VendorCommandResponseProcessor {
        return true;
    }

    public boolean processEvent(String atString, BluetoothDevice device) {
        if (device == null) {
            Log.w(TAG, "processVendorEvent device is null");
            return false;
        }

    private String getVendorIdFromAtCommand(String atString) {
        // Get event code
        int indexOfEqual = atString.indexOf('=');
        int indexOfColon = atString.indexOf(':');
@@ -148,13 +143,29 @@ class VendorCommandResponseProcessor {
        // replace all white spaces
        eventCode = eventCode.replaceAll("\\s+", "");

        return eventCode;
    }

    public boolean isAndroidAtCommand(String atString) {
        String eventCode = getVendorIdFromAtCommand(atString);
        Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
        if (vendorId == null) {
            return false;
        }
        return vendorId == BluetoothAssignedNumbers.GOOGLE;
    }

    public boolean processEvent(String atString, BluetoothDevice device) {
        if (device == null) {
            Log.w(TAG, "processVendorEvent device is null");
            return false;
        }

        String eventCode = getVendorIdFromAtCommand(atString);
        Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
        if (vendorId == null) {
            Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
            return false;
        } else if (vendorId == BluetoothAssignedNumbers.GOOGLE) {
            Log.i(TAG, "received +ANDROID event. Setting Audio policy to true");
            mService.setAudioPolicyRemoteSupported(device, true);
        } else {
            broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
            logD("process vendor event " + vendorId + ", " + eventCode + ", "
+105 −5
Original line number Diff line number Diff line
@@ -1439,18 +1439,118 @@ public class HeadsetStateMachineTest {
    /**
     * A test to validate received Android AT commands and processing
     */
    @Ignore("b/275668166")
    @Test
    public void testProcessAndroidAt() {
        setUpConnectedState();
    public void testCheckAndProcessAndroidAt() {
        // Commands that will be handled
        Assert.assertTrue(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=?" , mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        Assert.assertTrue(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=SINKAUDIOPOLICY,1,1,1" , mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        Assert.assertTrue(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=SINKAUDIOPOLICY,100,100,100" , mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(3)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        Assert.assertTrue(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=SINKAUDIOPOLICY,1,2,3,4,5" , mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);

        // Commands with correct format but will not be handled
        Assert.assertFalse(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=" , mTestDevice));
        Assert.assertFalse(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID: PROBE,1,\"`AB\"" , mTestDevice));
        Assert.assertFalse(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID= PROBE,1,\"`AB\"" , mTestDevice));
        Assert.assertFalse(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "AT+ANDROID=PROBE,1,1,\"PQGHRSBCTU__\"" , mTestDevice));

        // Incorrect format AT command
        Assert.assertFalse(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "RANDOM FORMAT" , mTestDevice));

        // Check no any AT result was sent for the failed ones
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(3)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
    }

    @Test
    public void testCheckAndProcessAndroidAt_replyAndroidAtFeatureRequest() {
        // Commands that will be handled
        Assert.assertTrue(mHeadsetStateMachine.checkAndProcessAndroidAt(
            "+ANDROID=?" , mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseString(
                mTestDevice, "+ANDROID: (SINKAUDIOPOLICY)");
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(
                mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    /**
     * A end to end test to validate received Android AT commands and processing
     */
    @Test
    public void testCehckAndProcessAndroidAtFromStateMachine() {
        // setAudioPolicyMetadata is invoked in HeadsetStateMachine.init() so start from 1
        int expectCallTimes = 1;

        // setup Audio Policy Feature
        setUpConnectedState();

        setUpAudioPolicy();
        // receive and set android policy
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
                        "+ANDROID=1,1,1,1", mTestDevice));
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
                        "+ANDROID=SINKAUDIOPOLICY,1,1,1", mTestDevice));
        expectCallTimes++;
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(expectCallTimes))
                .setAudioPolicyMetadata(anyObject(), anyObject());

        // receive and not set android policy
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
                        "AT+ANDROID=PROBE,1,1,\"PQGHRSBCTU__\"", mTestDevice));
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(expectCallTimes))
                .setAudioPolicyMetadata(anyObject(), anyObject());
    }

    /**
     * A test to verify whether the sink audio policy command is valid
     */
    @Test
    public void testProcessAndroidAtSinkAudioPolicy() {
        // expected format
        Assert.assertTrue(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0,0", mTestDevice));
        Assert.assertTrue(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0,1", mTestDevice));
        Assert.assertTrue(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,1,0", mTestDevice));
        Assert.assertTrue(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,1,0,0", mTestDevice));
        Assert.assertTrue(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,1,1,1", mTestDevice));

        // invalid format
        Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0", mTestDevice));
        Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0", mTestDevice));
        Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0,0,0", mTestDevice));
        Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,NOT,INT,TYPE", mTestDevice));
        Assert.assertFalse(setSinkAudioPolicyArgs("RANDOM,VALUE-#$%,*(&^", mTestDevice));

        // wrong device
        BluetoothDevice device = mAdapter.getRemoteDevice("01:01:01:01:01:01");
        Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0,0", device));
    }

    /**
     * set sink audio policy
     * @param arg body of the AT command
     * @return the result from processAndroidAtSinkAudioPolicy
     */
    private boolean setSinkAudioPolicyArgs(String arg, BluetoothDevice device) {
        Object[] args = HeadsetStateMachine.generateArgs(arg);
        return mHeadsetStateMachine.processAndroidAtSinkAudioPolicy(args, device);
    }

    /**
+85 −14

File changed.

Preview size limit exceeded, changes collapsed.

Loading