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

Commit 679fc4e9 authored by Jaikumar Ganesh's avatar Jaikumar Ganesh Committed by Android (Google) Code Review
Browse files

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

parents b1b5a1ab de07503a
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