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

Commit de07503a authored by Jaikumar Ganesh's avatar Jaikumar Ganesh
Browse files

Add HID to the state machine and add native call backs.

Change-Id: Ib9f3e476d4176bc04e23e7674dc54aa5a6417308
parent c3ee99d9
Loading
Loading
Loading
Loading
+246 −7
Original line number Diff line number Diff line
@@ -58,19 +58,24 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
    private static final String TAG = "BluetoothDeviceProfileState";
    private static final boolean DBG = true; //STOPSHIP - Change to false

    // TODO(): Restructure the state machine to make it scalable with regard to profiles.
    public static final int CONNECT_HFP_OUTGOING = 1;
    public static final int CONNECT_HFP_INCOMING = 2;
    public static final int CONNECT_A2DP_OUTGOING = 3;
    public static final int CONNECT_A2DP_INCOMING = 4;
    public static final int CONNECT_HID_OUTGOING = 5;
    public static final int CONNECT_HID_INCOMING = 6;

    public static final int DISCONNECT_HFP_OUTGOING = 5;
    private static final int DISCONNECT_HFP_INCOMING = 6;
    public static final int DISCONNECT_A2DP_OUTGOING = 7;
    public static final int DISCONNECT_A2DP_INCOMING = 8;
    public static final int DISCONNECT_HFP_OUTGOING = 50;
    private static final int DISCONNECT_HFP_INCOMING = 51;
    public static final int DISCONNECT_A2DP_OUTGOING = 52;
    public static final int DISCONNECT_A2DP_INCOMING = 53;
    public static final int DISCONNECT_HID_OUTGOING = 54;
    public static final int DISCONNECT_HID_INCOMING = 55;

    public static final int UNPAIR = 9;
    public static final int AUTO_CONNECT_PROFILES = 10;
    public static final int TRANSITION_TO_STABLE = 11;
    public static final int UNPAIR = 100;
    public static final int AUTO_CONNECT_PROFILES = 101;
    public static final int TRANSITION_TO_STABLE = 102;

    private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs

@@ -79,6 +84,8 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
    private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
    private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
    private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
    private OutgoingHid mOutgoingHid = new OutgoingHid();
    private IncomingHid mIncomingHid = new IncomingHid();

    private Context mContext;
    private BluetoothService mService;
@@ -89,6 +96,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
    private BluetoothDevice mDevice;
    private int mHeadsetState;
    private int mA2dpState;
    private int mHidState;

    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
@@ -125,6 +133,19 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                    newState == BluetoothA2dp.STATE_DISCONNECTED) {
                    sendMessage(TRANSITION_TO_STABLE);
                }
            } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
                int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
                int oldState =
                    intent.getIntExtra(BluetoothInputDevice.EXTRA_PREVIOUS_INPUT_DEVICE_STATE, 0);
                mHidState = newState;
                if (oldState == BluetoothInputDevice.STATE_CONNECTED &&
                    newState == BluetoothInputDevice.STATE_DISCONNECTED) {
                    sendMessage(DISCONNECT_HID_INCOMING);
                }
                if (newState == BluetoothInputDevice.STATE_CONNECTED ||
                    newState == BluetoothInputDevice.STATE_DISCONNECTED) {
                    sendMessage(TRANSITION_TO_STABLE);
                }
            } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
                if (!getCurrentState().equals(mBondedDevice)) {
                    Log.e(TAG, "State is: " + getCurrentState());
@@ -165,6 +186,8 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
        addState(mIncomingHandsfree);
        addState(mIncomingA2dp);
        addState(mOutgoingA2dp);
        addState(mOutgoingHid);
        addState(mIncomingHid);
        setInitialState(mBondedDevice);

        IntentFilter filter = new IntentFilter();
@@ -172,6 +195,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);

        mContext.registerReceiver(mBroadcastReceiver, filter);

@@ -224,6 +248,14 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                case DISCONNECT_A2DP_INCOMING:
                    transitionTo(mIncomingA2dp);
                    break;
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    transitionTo(mOutgoingHid);
                    break;
                case CONNECT_HID_INCOMING:
                case DISCONNECT_HID_INCOMING:
                    transitionTo(mIncomingHid);
                    break;
                case UNPAIR:
                    if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
                        sendMessage(DISCONNECT_HFP_OUTGOING);
@@ -233,6 +265,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                        sendMessage(DISCONNECT_A2DP_OUTGOING);
                        deferMessage(message);
                        break;
                    } else if (mHidState != BluetoothInputDevice.STATE_DISCONNECTED) {
                        sendMessage(DISCONNECT_HID_OUTGOING);
                        deferMessage(message);
                        break;
                    }
                    processCommand(UNPAIR);
                    break;
@@ -254,6 +290,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                              mA2dpService.getConnectedSinks().length == 0) {
                            mA2dpService.connectSink(mDevice);
                        }
                        if (mService.getInputDevicePriority(mDevice) ==
                              BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
                            mService.connectInputDevice(mDevice);
                        }
                    }
                    break;
                case TRANSITION_TO_STABLE:
@@ -342,6 +382,23 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                       deferMessage(deferMsg);
                    }
                    break;
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    deferMessage(message);
                    break;
                case CONNECT_HID_INCOMING:
                    transitionTo(mIncomingHid);
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break;
                case DISCONNECT_HID_INCOMING:
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break; // ignore
                case UNPAIR:
                case AUTO_CONNECT_PROFILES:
                    deferMessage(message);
@@ -409,6 +466,13 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                    // If this causes incoming HFP to fail, it is more of a headset problem
                    // since both connections are incoming ones.
                    break;
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    deferMessage(message);
                    break;
                case CONNECT_HID_INCOMING:
                case DISCONNECT_HID_INCOMING:
                     break; // ignore
                case UNPAIR:
                case AUTO_CONNECT_PROFILES:
                    deferMessage(message);
@@ -496,6 +560,23 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                case DISCONNECT_A2DP_INCOMING:
                    // Ignore, will be handled by Bluez
                    break;
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    deferMessage(message);
                    break;
                case CONNECT_HID_INCOMING:
                    transitionTo(mIncomingHid);
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break;
                case DISCONNECT_HID_INCOMING:
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break; // ignore
                case UNPAIR:
                case AUTO_CONNECT_PROFILES:
                    deferMessage(message);
@@ -561,6 +642,13 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                case DISCONNECT_A2DP_INCOMING:
                    // Ignore, will be handled by Bluez
                    break;
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    deferMessage(message);
                    break;
                case CONNECT_HID_INCOMING:
                case DISCONNECT_HID_INCOMING:
                     break; // ignore
                case UNPAIR:
                case AUTO_CONNECT_PROFILES:
                    deferMessage(message);
@@ -576,6 +664,143 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
    }


    private class OutgoingHid extends HierarchicalState {
        private boolean mStatus = false;
        private int mCommand;

        @Override
        protected void enter() {
            log("Entering OutgoingHid state with: " + getCurrentMessage().what);
            mCommand = getCurrentMessage().what;
            if (mCommand != CONNECT_HID_OUTGOING &&
                mCommand != DISCONNECT_HID_OUTGOING) {
                Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
            }
            mStatus = processCommand(mCommand);
            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
        }

        @Override
        protected boolean processMessage(Message message) {
            log("OutgoingHid State->Processing Message: " + message.what);
            Message deferMsg = new Message();
            switch(message.what) {
                // defer all outgoing messages
                case CONNECT_HFP_OUTGOING:
                case CONNECT_A2DP_OUTGOING:
                case CONNECT_HID_OUTGOING:
                case DISCONNECT_HFP_OUTGOING:
                case DISCONNECT_A2DP_OUTGOING:
                case DISCONNECT_HID_OUTGOING:
                    deferMessage(message);
                    break;

                case CONNECT_HFP_INCOMING:
                    transitionTo(mIncomingHandsfree);
                case CONNECT_A2DP_INCOMING:
                    transitionTo(mIncomingA2dp);

                    // Don't cancel HID outgoing as there is no guarantee it
                    // will get canceled.
                    // It might already be connected but we might not have got the
                    // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
                    // The worst case, the connection will fail, retry.
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break;
                case CONNECT_HID_INCOMING:
                  // Bluez will take care of the conflicts
                    transitionTo(mIncomingHid);
                    break;

                case DISCONNECT_HFP_INCOMING:
                case DISCONNECT_A2DP_INCOMING:
                    // At this point, we are already disconnected
                    // with HFP. Sometimes HID connection can
                    // fail due to the disconnection of HFP. So add a retry
                    // for the HID.
                    if (mStatus) {
                        deferMsg.what = mCommand;
                        deferMessage(deferMsg);
                    }
                    break;
                case DISCONNECT_HID_INCOMING:
                    // Ignore, will be handled by Bluez
                    break;

                case UNPAIR:
                case AUTO_CONNECT_PROFILES:
                    deferMessage(message);
                    break;
                case TRANSITION_TO_STABLE:
                    transitionTo(mBondedDevice);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

  private class IncomingHid extends HierarchicalState {
      private boolean mStatus = false;
      private int mCommand;

      @Override
      protected void enter() {
          log("Entering IncomingHid state with: " + getCurrentMessage().what);
          mCommand = getCurrentMessage().what;
          if (mCommand != CONNECT_HID_INCOMING &&
              mCommand != DISCONNECT_HID_INCOMING) {
              Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
          }
          mStatus = processCommand(mCommand);
          if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
      }

      @Override
      protected boolean processMessage(Message message) {
          log("IncomingHid State->Processing Message: " + message.what);
          Message deferMsg = new Message();
          switch(message.what) {
              case CONNECT_HFP_OUTGOING:
              case CONNECT_HFP_INCOMING:
              case DISCONNECT_HFP_OUTGOING:
              case CONNECT_A2DP_INCOMING:
              case CONNECT_A2DP_OUTGOING:
              case DISCONNECT_A2DP_OUTGOING:
              case CONNECT_HID_OUTGOING:
              case CONNECT_HID_INCOMING:
              case DISCONNECT_HID_OUTGOING:
                  deferMessage(message);
                  break;
              case DISCONNECT_HFP_INCOMING:
                  // Shouldn't happen but if does, we can handle it.
                  // Depends if the headset can handle it.
                  // Incoming HID will be handled by Bluez, Disconnect HFP
                  // the socket would have already been closed.
                  // ignore
                  break;
              case DISCONNECT_HID_INCOMING:
              case DISCONNECT_A2DP_INCOMING:
                  // Ignore, will be handled by Bluez
                  break;
              case UNPAIR:
              case AUTO_CONNECT_PROFILES:
                  deferMessage(message);
                  break;
              case TRANSITION_TO_STABLE:
                  transitionTo(mBondedDevice);
                  break;
              default:
                  return NOT_HANDLED;
          }
          return HANDLED;
      }
  }


    synchronized void cancelCommand(int command) {
        if (command == CONNECT_HFP_OUTGOING ) {
@@ -619,6 +844,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
            case CONNECT_A2DP_INCOMING:
                // ignore, Bluez takes care
                return true;
            case CONNECT_HID_OUTGOING:
                return mService.connectInputDeviceInternal(mDevice);
            case CONNECT_HID_INCOMING:
                return true;
            case DISCONNECT_HFP_OUTGOING:
                if (!mHeadsetServiceConnected) {
                    deferHeadsetMessage(command);
@@ -645,6 +874,15 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
                    return mA2dpService.disconnectSinkInternal(mDevice);
                }
                break;
            case DISCONNECT_HID_INCOMING:
                // ignore
                return true;
            case DISCONNECT_HID_OUTGOING:
                if (mService.getInputDevicePriority(mDevice) ==
                    BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
                    mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
                }
                return mService.disconnectInputDeviceInternal(mDevice);
            case UNPAIR:
                return mService.removeBondInternal(mDevice.getAddress());
            default:
@@ -653,6 +891,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine
        return false;
    }


    /*package*/ BluetoothDevice getDevice() {
        return mDevice;
    }
+10 −2
Original line number Diff line number Diff line
@@ -43,8 +43,9 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
    private static final boolean DBG = true; // STOPSHIP - change to false.
    private static final String TAG = "BluetoothProfileState";

    public static int HFP = 0;
    public static int A2DP = 1;
    public static final int HFP = 0;
    public static final int A2DP = 1;
    public static final int HID = 2;

    private static int TRANSITION_TO_STABLE = 100;

@@ -70,6 +71,12 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
                    newState == BluetoothA2dp.STATE_DISCONNECTED)) {
                    sendMessage(TRANSITION_TO_STABLE);
                }
            } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
                int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
                if (mProfile == HID && (newState == BluetoothInputDevice.STATE_CONNECTED ||
                    newState == BluetoothInputDevice.STATE_DISCONNECTED)) {
                    sendMessage(TRANSITION_TO_STABLE);
                }
            }
        }
    };
@@ -84,6 +91,7 @@ public class BluetoothProfileState extends HierarchicalStateMachine {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
        context.registerReceiver(mBroadcastReceiver, filter);
    }

+20 −0
Original line number Diff line number Diff line
@@ -693,6 +693,26 @@ class BluetoothEventLoop {
        }
    }

    private void onInputDeviceConnectionResult(String path, boolean result) {
        // Success case gets handled by Property Change signal
        if (!result) {
            String address = mBluetoothService.getAddressFromObjectPath(path);
            if (address == null) return;

            boolean connected = false;
            BluetoothDevice device = mAdapter.getRemoteDevice(address);
            int state = mBluetoothService.getInputDeviceState(device);
            if (state == BluetoothInputDevice.STATE_CONNECTING) {
                connected = false;
            } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
                connected = true;
            } else {
                Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
            }
            mBluetoothService.handleInputDevicePropertyChange(address, connected);
        }
    }

    private void onRestartRequired() {
        if (mBluetoothService.isEnabled()) {
            Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
+35 −4
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ public class BluetoothService extends IBluetooth.Stub {
    private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
    private final BluetoothProfileState mA2dpProfileState;
    private final BluetoothProfileState mHfpProfileState;
    private final BluetoothProfileState mHidProfileState;

    private BluetoothA2dpService mA2dpService;
    private final HashMap<BluetoothDevice, Integer> mInputDevices;
@@ -196,9 +197,11 @@ public class BluetoothService extends IBluetooth.Stub {
        mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
        mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
        mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
        mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);

        mHfpProfileState.start();
        mA2dpProfileState.start();
        mHidProfileState.start();

        IntentFilter filter = new IntentFilter();
        registerForAirplaneMode(filter);
@@ -1241,13 +1244,27 @@ public class BluetoothService extends IBluetooth.Stub {
            getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
            return false;
        }
        if(connectInputDeviceNative(objectPath)) {
            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
        BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
        if (state != null) {
            Message msg = new Message();
            msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING;
            msg.obj = state;
            mHidProfileState.sendMessage(msg);
            return true;
        }
        return false;
    }

    public synchronized boolean connectInputDeviceInternal(BluetoothDevice device) {
        String objectPath = getObjectPathFromAddress(device.getAddress());
        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
        if (!connectInputDeviceNative(objectPath)) {
            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED);
            return false;
        }
        return true;
    }

    public synchronized boolean disconnectInputDevice(BluetoothDevice device) {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                "Need BLUETOOTH_ADMIN permission");
@@ -1256,13 +1273,27 @@ public class BluetoothService extends IBluetooth.Stub {
        if (objectPath == null || getConnectedInputDevices().length == 0) {
            return false;
        }
        if(disconnectInputDeviceNative(objectPath)) {
            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
        BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
        if (state != null) {
            Message msg = new Message();
            msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING;
            msg.obj = state;
            mHidProfileState.sendMessage(msg);
            return true;
        }
        return false;
    }

    public synchronized boolean disconnectInputDeviceInternal(BluetoothDevice device) {
        String objectPath = getObjectPathFromAddress(device.getAddress());
        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
        if (!disconnectInputDeviceNative(objectPath)) {
            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED);
            return false;
        }
        return true;
    }

    public synchronized int getInputDeviceState(BluetoothDevice device) {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");

+29 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ static jmethodID method_onAgentAuthorize;
static jmethodID method_onAgentCancel;

static jmethodID method_onInputDevicePropertyChanged;
static jmethodID method_onInputDeviceConnectionResult;

typedef event_loop_native_data_t native_data_t;

@@ -120,7 +121,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
                                               "(Ljava/lang/String;II)V");
    method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged",
                                                      "(Ljava/lang/String;[Ljava/lang/String;)V");

    method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult",
                                                      "(Ljava/lang/String;Z)V");

    field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
#endif
@@ -1245,6 +1247,32 @@ done:
    env->DeleteLocalRef(addr);
    free(user);
}

void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *n) {
    LOGV(__FUNCTION__);

    native_data_t *nat = (native_data_t *)n;
    const char *path = (const char *)user;
    DBusError err;
    dbus_error_init(&err);
    JNIEnv *env;
    nat->vm->GetEnv((void**)&env, nat->envVer);

    bool result = JNI_TRUE;
    if (dbus_set_error_from_message(&err, msg)) {
        LOG_AND_FREE_DBUS_ERROR(&err);
        result = JNI_FALSE;
    }
    LOGV("... Device Path = %s, result = %d", path, result);
    jstring jPath = env->NewStringUTF(path);
    env->CallVoidMethod(nat->me,
                        method_onInputDeviceConnectionResult,
                        jPath,
                        result);
    env->DeleteLocalRef(jPath);
    free(user);
}

#endif

static JNINativeMethod sMethods[] = {
Loading