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

Commit 0b13de7e authored by David Duarte's avatar David Duarte Committed by Android (Google) Code Review
Browse files

Merge changes from topic "bluetooth_pb1" into udc-dev

* changes:
  Reject ANDROID AT command if the first param is not String
  Reland "Refine AT command of BluetoothSinkAudioPolicy"
  Clear identity bit when passing address to filter
  Keep the IRK when the scan is finished
  Enhance CLCC inference
  Display Bluetooth Disconnected Message
  Add init_flag for SCO LC3 codec
  Log bluetooth state change in metrics
  Add flag for asha_phy_update_retry_limit
  Add ASHA packet drop frequency limit
  Break out stack::include::smp_status.h
parents 43096edf c195ca72
Loading
Loading
Loading
Loading
+27 −17
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.bluetooth.BluetoothPrefs;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
@@ -321,10 +322,10 @@ public class AvrcpControllerService extends ProfileService {
     * Get a List of MediaItems that are children of the specified media Id
     *
     * @param parentMediaId The player or folder to get the contents of
     * @return List of Children if available, an empty list if there are none,
     * or null if a search must be performed.
     * @return List of Children if available, an empty list if there are none, or null if a search
     *     must be performed.
     */
    public synchronized List<MediaItem> getContents(String parentMediaId) {
    public synchronized BrowseResult getContents(String parentMediaId) {
        if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");

        BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
@@ -337,28 +338,37 @@ public class AvrcpControllerService extends ProfileService {
                }
            }
        }

        // If we don't find a node in the tree then do not have any way to browse for the contents.
        // Return an empty list instead.
        if (requestedNode == null) {
            if (DBG) Log.d(TAG, "Didn't find a node");
            return new ArrayList(0);
        } else {
            return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID);
        }
        if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) {
            return new BrowseResult(null, BrowseResult.NO_DEVICE_CONNECTED);
        }
        // If we found a node and it belongs to a device then go ahead and make it active
        BluetoothDevice device = requestedNode.getDevice();
        if (device != null) {
            setActiveDevice(device);
        }

        List<MediaItem> contents = requestedNode.getContents();

        if (DBG) Log.d(TAG, "Returning contents");
        if (!requestedNode.isCached()) {
            if (DBG) Log.d(TAG, "node is not cached");
            refreshContents(requestedNode);
            /* Ongoing downloads can have partial results and we want to make sure they get sent
             * to the client. If a download gets kicked off as a result of this request, the
             * contents will be null until the first results arrive.
             */
            return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING);
        }
            if (DBG) Log.d(TAG, "Returning contents");
            return requestedNode.getContents();
        }
        return new BrowseResult(contents, BrowseResult.SUCCESS);
    }


    @Override
    protected IProfileServiceBinder initBinder() {
        return new AvrcpControllerServiceBinder(this);
+66 −6
Original line number Diff line number Diff line
@@ -132,11 +132,66 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
        mReceiver = null;
    }

    List<MediaItem> getContents(final String parentMediaId) {
    /**
     * BrowseResult is used to return the contents of a node along with a status. The status is
     * used to indicate success, a pending download, or error conditions. BrowseResult is used in
     * onLoadChildren() and getContents() in BluetoothMediaBrowserService and in getContents() in
     * AvrcpControllerService.
     * The following statuses have been implemented:
     * 1. SUCCESS - Contents have been retrieved successfully.
     * 2. DOWNLOAD_PENDING - Download is in progress and may or may not have contents to return.
     * 3. NO_DEVICE_CONNECTED - If no device is connected there are no contents to be retrieved.
     * 4. ERROR_MEDIA_ID_INVALID - Contents could not be retrieved as the media ID is invalid.
     * 5. ERROR_NO_AVRCP_SERVICE - Contents could not be retrieved as AvrcpControllerService is not
     *                             connected.
     */
    public static class BrowseResult {
        // Possible statuses for onLoadChildren
        public static final byte SUCCESS = 0x00;
        public static final byte DOWNLOAD_PENDING = 0x01;
        public static final byte NO_DEVICE_CONNECTED = 0x02;
        public static final byte ERROR_MEDIA_ID_INVALID = 0x03;
        public static final byte ERROR_NO_AVRCP_SERVICE = 0x04;

        private List<MediaItem> mResults;
        private final byte mStatus;

        List<MediaItem> getResults() {
            return mResults;
        }

        byte getStatus() {
            return mStatus;
        }

        String getStatusString() {
            switch (mStatus) {
                case DOWNLOAD_PENDING:
                    return "DOWNLOAD_PENDING";
                case SUCCESS:
                    return "SUCCESS";
                case NO_DEVICE_CONNECTED:
                    return "NO_DEVICE_CONNECTED";
                case ERROR_MEDIA_ID_INVALID:
                    return "ERROR_MEDIA_ID_INVALID";
                case ERROR_NO_AVRCP_SERVICE:
                    return "ERROR_NO_AVRCP_SERVICE";
                default:
                    return "UNDEFINED_ERROR_CASE";
            }
        }

        BrowseResult(List<MediaItem> results, byte status) {
            mResults = results;
            mStatus = status;
        }
    }

    BrowseResult getContents(final String parentMediaId) {
        AvrcpControllerService avrcpControllerService =
                AvrcpControllerService.getAvrcpControllerService();
        if (avrcpControllerService == null) {
            return new ArrayList(0);
            return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_NO_AVRCP_SERVICE);
        } else {
            return avrcpControllerService.getContents(parentMediaId);
        }
@@ -173,11 +228,16 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
    public synchronized void onLoadChildren(final String parentMediaId,
            final Result<List<MediaItem>> result) {
        if (DBG) Log.d(TAG, "onLoadChildren parentMediaId= " + parentMediaId);
        List<MediaItem> contents = getContents(parentMediaId);
        if (contents == null) {
        BrowseResult contents = getContents(parentMediaId);
        byte status = contents.getStatus();
        if (status == BrowseResult.DOWNLOAD_PENDING && contents == null) {
            Log.i(TAG, "Download pending - no contents, id= " + parentMediaId);
            result.detach();
        } else {
            result.sendResult(contents);
            if (DBG) {
                Log.d(TAG, "id= " + parentMediaId + ", status= " + contents.getStatusString());
            }
            result.sendResult(contents.getResults());
        }
    }

+48 −37
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,96 @@ 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");
            if (!(args[0] instanceof String)) {
                Log.w(TAG, "Incorrect type of Android AT command!");
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                return;
                return true;
            }

            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 +2065,7 @@ public class HeadsetStateMachine extends StateMachine {
                .setActiveDevicePolicyAfterConnection(connectingTimePolicy)
                .setInBandRingtonePolicy(inbandPolicy)
                .build());
        return true;
    }

    /**
@@ -2129,6 +2137,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 + ", "
Loading