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

Commit d1e98e42 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 10245577 from ae0874d9 to udc-qpr1-release

Change-Id: I2bcb95db2d17e886fbc5984698746fa2ece6a261
parents 8608b2c9 ae0874d9
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