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

Commit 8de4272c authored by Deqiang Chen's avatar Deqiang Chen Committed by Gerrit Code Review
Browse files

Merge changes from topic "HfpClientVendorAtCommand"

* changes:
  Support vendor AT command and response in Hfp client
  Refactor NativeInterace to wrap static native function as public non-static function
parents 33ea64a8 70f4b053
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ static jmethodID method_onSubscriberInfo;
static jmethodID method_onInBandRing;
static jmethodID method_onLastVoiceTagNumber;
static jmethodID method_onRingIndication;
static jmethodID method_onUnknownEvent;

static jbyteArray marshall_bda(const RawAddress* bd_addr) {
  CallbackEnv sCallbackEnv(__func__);
@@ -322,6 +323,20 @@ static void ring_indication_cb(const RawAddress* bd_addr) {
                               addr.get());
}

static void unknown_event_cb(const RawAddress* bd_addr,
                             const char* eventString) {
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid()) return;

  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
  if (!addr.get()) return;

  ScopedLocalRef<jstring> js_event(sCallbackEnv.get(),
                                   sCallbackEnv->NewStringUTF(eventString));
  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownEvent,
                               js_event.get(), addr.get());
}

static bthf_client_callbacks_t sBluetoothHfpClientCallbacks = {
    sizeof(sBluetoothHfpClientCallbacks),
    connection_state_cb,
@@ -345,6 +360,7 @@ static bthf_client_callbacks_t sBluetoothHfpClientCallbacks = {
    in_band_ring_cb,
    last_voice_tag_number_cb,
    ring_indication_cb,
    unknown_event_cb,
};

static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -377,6 +393,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
  method_onLastVoiceTagNumber =
      env->GetMethodID(clazz, "onLastVoiceTagNumber", "(Ljava/lang/String;[B)V");
  method_onRingIndication = env->GetMethodID(clazz, "onRingIndication", "([B)V");
  method_onUnknownEvent =
      env->GetMethodID(clazz, "onUnknownEvent", "(Ljava/lang/String;[B)V");

  ALOGI("%s succeeds", __func__);
}
+1 −0
Original line number Diff line number Diff line
@@ -170,6 +170,7 @@ public final class HeadsetClientHalConstants {
    // used for sending vendor specific AT cmds to AG.

    static final int HANDSFREECLIENT_AT_CMD_NREC = 15;
    static final int HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD = 16;

    // Flag to check for local NREC support
    static final boolean HANDSFREECLIENT_NREC_SUPPORTED = true;
+33 −4
Original line number Diff line number Diff line
@@ -80,8 +80,8 @@ public class HeadsetClientService extends ProfileService {
        }

        // Setup the JNI service
        mNativeInterface = new NativeInterface();
        mNativeInterface.initializeNative();
        mNativeInterface = NativeInterface.getInstance();
        mNativeInterface.initialize();

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        if (mAudioManager == null) {
@@ -137,7 +137,7 @@ public class HeadsetClientService extends ProfileService {
        mSmThread.quit();
        mSmThread = null;

        mNativeInterface.cleanupNative();
        mNativeInterface.cleanup();
        mNativeInterface = null;

        return true;
@@ -430,6 +430,15 @@ public class HeadsetClientService extends ProfileService {
            return service.getCurrentAgEvents(device);
        }

        @Override
        public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
            HeadsetClientService service = getService();
            if (service == null) {
                return false;
            }
            return service.sendVendorAtCommand(device, vendorId, atCommand);
        }

        @Override
        public Bundle getCurrentAgFeatures(BluetoothDevice device) {
            HeadsetClientService service = getService();
@@ -820,6 +829,26 @@ public class HeadsetClientService extends ProfileService {
        return true;
    }

    /** Send vendor AT command. */
    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        HeadsetClientStateMachine sm = getStateMachine(device);
        if (sm == null) {
            Log.e(TAG, "Cannot allocate SM for device " + device);
            return false;
        }

        int connectionState = sm.getConnectionState(device);
        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
            return false;
        }

        Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND,
                                       vendorId, 0, atCommand);
        sm.sendMessage(msg);
        return true;
    }

    public Bundle getCurrentAgEvents(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        HeadsetClientStateMachine sm = getStateMachine(device);
@@ -885,7 +914,7 @@ public class HeadsetClientService extends ProfileService {

        // Allocate a new SM
        Log.d(TAG, "Creating a new state machine");
        sm = mSmFactory.make(this, mSmThread);
        sm = mSmFactory.make(this, mSmThread, mNativeInterface);
        mStateMachineMap.put(device, sm);
        return sm;
    }
+51 −29
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ public class HeadsetClientStateMachine extends StateMachine {
    public static final int SEND_DTMF = 17;
    public static final int EXPLICIT_CALL_TRANSFER = 18;
    public static final int DISABLE_NREC = 20;
    public static final int SEND_VENDOR_AT_COMMAND = 21;

    // internal actions
    private static final int QUERY_CURRENT_CALLS = 50;
@@ -174,7 +175,9 @@ public class HeadsetClientStateMachine extends StateMachine {
    // This is returned when requesting focus from AudioManager
    private AudioFocusRequest mAudioFocusRequest;

    private AudioManager mAudioManager;
    private final AudioManager mAudioManager;
    private final NativeInterface mNativeInterface;
    private final VendorCommandResponseProcessor mVendorProcessor;

    // Accessor for the states, useful for reusing the state machines
    public IState getDisconnectedState() {
@@ -264,7 +267,7 @@ public class HeadsetClientStateMachine extends StateMachine {
    private boolean queryCallsStart() {
        logD("queryCallsStart");
        clearPendingAction();
        NativeInterface.queryCurrentCallsNative(getByteAddress(mCurrentDevice));
        mNativeInterface.queryCurrentCalls(getByteAddress(mCurrentDevice));
        addQueuedAction(QUERY_CURRENT_CALLS, 0);
        return true;
    }
@@ -487,7 +490,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            routeHfpAudio(true);
        }

        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
            addQueuedAction(ACCEPT_CALL, action);
        } else {
            Log.e(TAG, "ERROR: Couldn't accept a call, action:" + action);
@@ -526,8 +529,8 @@ public class HeadsetClientStateMachine extends StateMachine {
                return;
        }

        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
            logD("Reject call action " + action);
        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
            addQueuedAction(REJECT_CALL, action);
        } else {
            Log.e(TAG, "ERROR: Couldn't reject a call, action:" + action);
@@ -551,7 +554,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            action = HeadsetClientHalConstants.CALL_ACTION_CHLD_2;
        }

        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
            addQueuedAction(HOLD_CALL, action);
        } else {
            Log.e(TAG, "ERROR: Couldn't hold a call, action:" + action);
@@ -572,7 +575,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
        }
        if (c != null) {
            if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
            if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice), action, 0)) {
                addQueuedAction(TERMINATE_CALL, action);
            } else {
                Log.e(TAG, "ERROR: Couldn't terminate outgoing call");
@@ -590,7 +593,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            return;
        }

        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice),
                HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, idx)) {
            addQueuedAction(ENTER_PRIVATE_MODE, c);
        } else {
@@ -606,7 +609,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            return;
        }

        if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
        if (mNativeInterface.handleCallAction(getByteAddress(mCurrentDevice),
                HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
            addQueuedAction(EXPLICIT_CALL_TRANSFER);
        } else {
@@ -659,11 +662,15 @@ public class HeadsetClientStateMachine extends StateMachine {
        return b;
    }

    HeadsetClientStateMachine(HeadsetClientService context, Looper looper) {
    HeadsetClientStateMachine(HeadsetClientService context, Looper looper,
                              NativeInterface nativeInterface) {
        super(TAG, looper);
        mService = context;
        mNativeInterface = nativeInterface;
        mAudioManager = mService.getAudioManager();

        mVendorProcessor = new VendorCommandResponseProcessor(mService, mNativeInterface);

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
        mAudioWbs = false;
@@ -699,9 +706,11 @@ public class HeadsetClientStateMachine extends StateMachine {
        setInitialState(mDisconnected);
    }

    static HeadsetClientStateMachine make(HeadsetClientService context, Looper l) {
    static HeadsetClientStateMachine make(HeadsetClientService context, Looper looper,
                                          NativeInterface nativeInterface) {
        logD("make");
        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, l);
        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, looper,
                                                                        nativeInterface);
        hfcsm.start();
        return hfcsm;
    }
@@ -739,7 +748,7 @@ public class HeadsetClientStateMachine extends StateMachine {
    public void doQuit() {
        logD("doQuit");
        if (mCurrentDevice != null) {
            NativeInterface.disconnectNative(getByteAddress(mCurrentDevice));
            mNativeInterface.disconnect(getByteAddress(mCurrentDevice));
        }
        routeHfpAudio(false);
        returnAudioFocusIfNecessary();
@@ -825,7 +834,7 @@ public class HeadsetClientStateMachine extends StateMachine {
            switch (message.what) {
                case CONNECT:
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!NativeInterface.connectNative(getByteAddress(device))) {
                    if (!mNativeInterface.connect(getByteAddress(device))) {
                        // No state transition is involved, fire broadcast immediately
                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                                BluetoothProfile.STATE_DISCONNECTED);
@@ -871,7 +880,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                                + " bondState=" + device.getBondState());
                        // reject the connection and stay in Disconnected state
                        // itself
                        NativeInterface.disconnectNative(getByteAddress(device));
                        mNativeInterface.disconnect(getByteAddress(device));
                        // the other profile connection should be initiated
                        AdapterService adapterService = AdapterService.getAdapterService();
                        // No state transition is involved, fire broadcast immediately
@@ -985,7 +994,7 @@ public class HeadsetClientStateMachine extends StateMachine {

                    // We do not support devices which do not support enhanced call status (ECS).
                    if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECS) == 0) {
                        NativeInterface.disconnectNative(getByteAddress(device));
                        mNativeInterface.disconnect(getByteAddress(device));
                        return;
                    }

@@ -993,7 +1002,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                    if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED && (
                            (mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR)
                                    == HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
                        if (NativeInterface.sendATCmdNative(getByteAddress(mCurrentDevice),
                        if (mNativeInterface.sendATCmd(getByteAddress(mCurrentDevice),
                                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC, 1, 0,
                                null)) {
                            addQueuedAction(DISABLE_NREC);
@@ -1083,20 +1092,20 @@ public class HeadsetClientStateMachine extends StateMachine {
                        // already connected to this device, do nothing
                        break;
                    }
                    NativeInterface.connectNative(getByteAddress(device));
                    mNativeInterface.connect(getByteAddress(device));
                    break;
                case DISCONNECT:
                    BluetoothDevice dev = (BluetoothDevice) message.obj;
                    if (!mCurrentDevice.equals(dev)) {
                        break;
                    }
                    if (!NativeInterface.disconnectNative(getByteAddress(dev))) {
                    if (!mNativeInterface.disconnect(getByteAddress(dev))) {
                        Log.e(TAG, "disconnectNative failed for " + dev);
                    }
                    break;

                case CONNECT_AUDIO:
                    if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) {
                    if (!mNativeInterface.connectAudio(getByteAddress(mCurrentDevice))) {
                        Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice);
                        // No state transition is involved, fire broadcast immediately
                        broadcastAudioState(mCurrentDevice,
@@ -1108,14 +1117,14 @@ public class HeadsetClientStateMachine extends StateMachine {
                    break;

                case DISCONNECT_AUDIO:
                    if (!NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
                    if (!mNativeInterface.disconnectAudio(getByteAddress(mCurrentDevice))) {
                        Log.e(TAG, "ERROR: Couldn't disconnect Audio for device " + mCurrentDevice);
                    }
                    break;

                case VOICE_RECOGNITION_START:
                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
                        if (NativeInterface.startVoiceRecognitionNative(
                        if (mNativeInterface.startVoiceRecognition(
                                    getByteAddress(mCurrentDevice))) {
                            addQueuedAction(VOICE_RECOGNITION_START);
                        } else {
@@ -1126,7 +1135,7 @@ public class HeadsetClientStateMachine extends StateMachine {

                case VOICE_RECOGNITION_STOP:
                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
                        if (NativeInterface.stopVoiceRecognitionNative(
                        if (mNativeInterface.stopVoiceRecognition(
                                    getByteAddress(mCurrentDevice))) {
                            addQueuedAction(VOICE_RECOGNITION_STOP);
                        } else {
@@ -1135,6 +1144,13 @@ public class HeadsetClientStateMachine extends StateMachine {
                    }
                    break;

                case SEND_VENDOR_AT_COMMAND: {
                    int vendorId = message.arg1;
                    String atCommand = (String) (message.obj);
                    mVendorProcessor.sendCommand(vendorId, atCommand, mCurrentDevice);
                    break;
                }

                // Called only for Mute/Un-mute - Mic volume change is not allowed.
                case SET_MIC_VOLUME:
                    break;
@@ -1146,7 +1162,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                        logD("Volume" + amVol + ":" + mCommandedSpeakerVolume);
                        // Volume was changed by a 3rd party
                        mCommandedSpeakerVolume = -1;
                        if (NativeInterface.setVolumeNative(getByteAddress(mCurrentDevice),
                        if (mNativeInterface.setVolume(getByteAddress(mCurrentDevice),
                                HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
                            addQueuedAction(SET_SPEAKER_VOLUME);
                        }
@@ -1157,7 +1173,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                    BluetoothHeadsetClientCall c = (BluetoothHeadsetClientCall) message.obj;
                    mCalls.put(HF_ORIGINATED_CALL_ID, c);

                    if (NativeInterface.dialNative(getByteAddress(mCurrentDevice), c.getNumber())) {
                    if (mNativeInterface.dial(getByteAddress(mCurrentDevice), c.getNumber())) {
                        addQueuedAction(DIAL_NUMBER, c.getNumber());
                        // Start looping on calling current calls.
                        sendMessage(QUERY_CURRENT_CALLS);
@@ -1189,7 +1205,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                    explicitCallTransfer();
                    break;
                case SEND_DTMF:
                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice),
                    if (mNativeInterface.sendDtmf(getByteAddress(mCurrentDevice),
                            (byte) message.arg1)) {
                        addQueuedAction(SEND_DTMF);
                    } else {
@@ -1197,7 +1213,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                    }
                    break;
                case SUBSCRIBER_INFO:
                    if (NativeInterface.retrieveSubscriberInfoNative(
                    if (mNativeInterface.retrieveSubscriberInfo(
                            getByteAddress(mCurrentDevice))) {
                        addQueuedAction(SUBSCRIBER_INFO);
                    } else {
@@ -1248,7 +1264,7 @@ public class HeadsetClientStateMachine extends StateMachine {

                            if (mIndicatorNetworkState
                                    == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
                                if (NativeInterface.queryCurrentOperatorNameNative(
                                if (mNativeInterface.queryCurrentOperatorName(
                                        getByteAddress(mCurrentDevice))) {
                                    addQueuedAction(QUERY_OPERATOR_NAME);
                                } else {
@@ -1385,6 +1401,12 @@ public class HeadsetClientStateMachine extends StateMachine {
                            // implemented (by the client of this service). Use the
                            // CALL_STATE_INCOMING (and similar) handle ringing.
                            break;
                        case StackEvent.EVENT_TYPE_UNKNOWN_EVENT:
                            if (!mVendorProcessor.processEvent(event.valueString, event.device)) {
                                Log.e(TAG, "Unknown event :" + event.valueString
                                        + " for device " + event.device);
                            }
                            break;
                        default:
                            Log.e(TAG, "Unknown stack event: " + event.type);
                            break;
@@ -1529,7 +1551,7 @@ public class HeadsetClientStateMachine extends StateMachine {
                     * StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, that triggers State
                     * Machines state changing
                     */
                    if (NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
                    if (mNativeInterface.disconnectAudio(getByteAddress(mCurrentDevice))) {
                        routeHfpAudio(false);
                        returnAudioFocusIfNecessary();
                    }
+7 −2
Original line number Diff line number Diff line
@@ -20,7 +20,12 @@ import android.os.HandlerThread;

// Factory so that StateMachine objected can be mocked
public class HeadsetClientStateMachineFactory {
    public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t) {
        return HeadsetClientStateMachine.make(context, t.getLooper());
    /**
     * Factory method to create state machine for headset client
     *
     */
    public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t,
            NativeInterface nativeInterface) {
        return HeadsetClientStateMachine.make(context, t.getLooper(), nativeInterface);
    }
}
Loading