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

Commit 2202c6c3 authored by David Duarte's avatar David Duarte Committed by Automerger Merge Worker
Browse files

Merge changes I7cea765c,Idb4bea1b,I7bf47376,I0cf56537,I4ffb6027 into udc-dev am: ca4985b4

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


    static final int HFP_SET_AUDIO_POLICY = 1;

    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;


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


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


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


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


            Object[] args = generateArgs(arg);
            Object[] args = generateArgs(arg);


            String type = (String) args[0];
            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];


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

        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
        Log.w(TAG, "Unhandled +ANDROID command: " + atString);
        return false;
    }
    }


    private void processAndroidAtFeatureRequest(BluetoothDevice device) {
    private void processAndroidAtFeatureRequest(BluetoothDevice device) {
        /*
        /*
            replying with +ANDROID: (<feature1>, <feature2>, ...)
            replying with +ANDROID=1
            currently we only support one type of feature: SINKAUDIOPOLICY
            here, 1 is the feature id for audio policy

            currently we only support one type of feature
        */
        */
        mNativeInterface.atResponseString(device,
        mNativeInterface.atResponseString(device,
                BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID
                BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID
                + ": (" + BluetoothSinkAudioPolicy.HFP_SET_SINK_AUDIO_POLICY_ID + ")");
                + ": " + HFP_SET_AUDIO_POLICY);
    }
    }


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


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


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


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


    private final boolean mClccPollDuringCall;
    private final boolean mClccPollDuringCall;


    private static final int CALL_AUDIO_POLICY_FEATURE_ID = 1;

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


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

                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
                        case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
                        case StackEvent.EVENT_TYPE_CURRENT_CALLS:
                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
                        case StackEvent.EVENT_TYPE_OPERATOR_NAME:
@@ -2026,44 +2027,6 @@ public class HeadsetClientStateMachine extends StateMachine {
        HfpClientConnectionService.onAudioStateChanged(device, newState, prevState);
        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)
    // This method does not check for error condition (newState == prevState)
    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
        logD("Connection state " + device + ": " + prevState + "->" + newState);
        logD("Connection state " + device + ": " + prevState + "->" + newState);
@@ -2263,7 +2226,7 @@ public class HeadsetClientStateMachine extends StateMachine {


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


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

        // Get event code
        // Get event code
        int indexOfEqual = atString.indexOf('=');
        int indexOfEqual = atString.indexOf('=');
        int indexOfColon = atString.indexOf(':');
        int indexOfColon = atString.indexOf(':');
@@ -143,29 +148,13 @@ class VendorCommandResponseProcessor {
        // replace all white spaces
        // replace all white spaces
        eventCode = eventCode.replaceAll("\\s+", "");
        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);
        Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
        if (vendorId == null) {
        if (vendorId == null) {
            Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
            Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
            return false;
            return false;
        } else if (vendorId == BluetoothAssignedNumbers.GOOGLE) {
            Log.i(TAG, "received +ANDROID event. Setting Audio policy to true");
            mService.setAudioPolicyRemoteSupported(device, true);
        } else {
        } else {
            broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
            broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
            logD("process vendor event " + vendorId + ", " + eventCode + ", "
            logD("process vendor event " + vendorId + ", " + eventCode + ", "
+5 −105
Original line number Original line Diff line number Diff line
@@ -1439,118 +1439,18 @@ public class HeadsetStateMachineTest {
    /**
    /**
     * A test to validate received Android AT commands and processing
     * A test to validate received Android AT commands and processing
     */
     */
    @Ignore("b/275668166")
    @Test
    @Test
    public void testCheckAndProcessAndroidAt() {
    public void testProcessAndroidAt() {
        // 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();
        setUpConnectedState();

        // setup Audio Policy Feature
        setUpAudioPolicy();
        setUpAudioPolicy();
        // receive and set android policy
        // receive and set android policy
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
                        "+ANDROID=SINKAUDIOPOLICY,1,1,1", mTestDevice));
                        "+ANDROID=1,1,1,1", mTestDevice));
        expectCallTimes++;
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(expectCallTimes))
                .setAudioPolicyMetadata(anyObject(), anyObject());
                .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);
    }
    }


    /**
    /**
+14 −85

File changed.

Preview size limit exceeded, changes collapsed.

Loading