Bug 899014 - [Bluetooth][HID] HID dom bluetooth implementation, r=echou
authorBen Tian <btian@mozilla.com>
Mon, 12 Aug 2013 17:32:25 +0800
changeset 150411 95da71532621656886d1adf99a0649b48ae185b8
parent 150410 a9980741489c33187751d5ce5caf4643af84f7f5
child 150412 ce94c5f1ad3f6de9206e89192a6b4faa064969e0
push idunknown
push userunknown
push dateunknown
reviewersechou
bugs899014
milestone26.0a1
Bug 899014 - [Bluetooth][HID] HID dom bluetooth implementation, r=echou
dom/bluetooth/BluetoothCommon.h
dom/bluetooth/BluetoothHidManager.cpp
dom/bluetooth/BluetoothHidManager.h
dom/bluetooth/BluetoothService.h
dom/bluetooth/BluetoothUuid.h
dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
dom/bluetooth/ipc/BluetoothServiceChildProcess.h
dom/bluetooth/linux/BluetoothDBusService.cpp
dom/bluetooth/linux/BluetoothDBusService.h
dom/bluetooth/moz.build
dom/messages/SystemMessagePermissionsChecker.jsm
--- a/dom/bluetooth/BluetoothCommon.h
+++ b/dom/bluetooth/BluetoothCommon.h
@@ -52,16 +52,17 @@ extern bool gBluetoothDebugFlag;
 #define KEY_ADAPTER      "/B2G/bluetooth/adapter"
 
 /**
  * When the connection status of a Bluetooth profile is changed, we'll notify
  * observers which register the following topics.
  */
 #define BLUETOOTH_A2DP_STATUS_CHANGED_ID "bluetooth-a2dp-status-changed"
 #define BLUETOOTH_HFP_STATUS_CHANGED_ID  "bluetooth-hfp-status-changed"
+#define BLUETOOTH_HID_STATUS_CHANGED_ID  "bluetooth-hid-status-changed"
 #define BLUETOOTH_SCO_STATUS_CHANGED_ID  "bluetooth-sco-status-changed"
 
 /**
  * When the connection status of a Bluetooth profile is changed, we'll
  * distribute one of the following events.
  */
 #define A2DP_STATUS_CHANGED_ID               "a2dpstatuschanged"
 #define HFP_STATUS_CHANGED_ID                "hfpstatuschanged"
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/BluetoothHidManager.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "BluetoothHidManager.h"
+
+#include "BluetoothCommon.h"
+#include "BluetoothService.h"
+#include "BluetoothUtils.h"
+
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserverService.h"
+
+using namespace mozilla;
+USING_BLUETOOTH_NAMESPACE
+
+namespace {
+  StaticRefPtr<BluetoothHidManager> sBluetoothHidManager;
+  bool sInShutdown = false;
+} // anonymous namespace
+
+NS_IMETHODIMP
+BluetoothHidManager::Observe(nsISupports* aSubject,
+                             const char* aTopic,
+                             const PRUnichar* aData)
+{
+  MOZ_ASSERT(sBluetoothHidManager);
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    HandleShutdown();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "BluetoothHidManager got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+BluetoothHidManager::BluetoothHidManager()
+  : mConnected(false)
+{
+}
+
+bool
+BluetoothHidManager::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE(obs, false);
+  if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
+    BT_WARNING("Failed to add shutdown observer!");
+    return false;
+  }
+
+  return true;
+}
+
+BluetoothHidManager::~BluetoothHidManager()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+  if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
+    BT_WARNING("Failed to remove shutdown observer!");
+  }
+}
+
+//static
+BluetoothHidManager*
+BluetoothHidManager::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we already exist, exit early
+  if (sBluetoothHidManager) {
+    return sBluetoothHidManager;
+  }
+
+  // If we're in shutdown, don't create a new instance
+  NS_ENSURE_FALSE(sInShutdown, nullptr);
+
+  // Create a new instance, register, and return
+  BluetoothHidManager* manager = new BluetoothHidManager();
+  NS_ENSURE_TRUE(manager->Init(), nullptr);
+
+  sBluetoothHidManager = manager;
+  return sBluetoothHidManager;
+}
+
+void
+BluetoothHidManager::HandleShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  sInShutdown = true;
+  Disconnect();
+  sBluetoothHidManager = nullptr;
+}
+
+bool
+BluetoothHidManager::Connect(const nsAString& aDeviceAddress,
+                             BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
+
+  NS_ENSURE_FALSE(sInShutdown, false);
+  NS_ENSURE_FALSE(mConnected, false);
+
+  mDeviceAddress = aDeviceAddress;
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, false);
+  nsresult rv = bs->SendInputMessage(aDeviceAddress,
+                                     NS_LITERAL_STRING("Connect"),
+                                     aRunnable);
+
+  return NS_SUCCEEDED(rv);
+}
+
+void
+BluetoothHidManager::Disconnect()
+{
+  NS_ENSURE_TRUE_VOID(mConnected);
+
+  MOZ_ASSERT(!mDeviceAddress.IsEmpty());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+  bs->SendInputMessage(mDeviceAddress,
+                       NS_LITERAL_STRING("Disconnect"),
+                       nullptr);
+}
+
+bool BluetoothHidManager::IsConnected()
+{
+  return mConnected;
+}
+
+void BluetoothHidManager::HandleInputPropertyChanged(const BluetoothSignal& aSignal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aSignal.value().get_ArrayOfBluetoothNamedValue();
+  MOZ_ASSERT(arr.Length() == 1);
+
+  const nsString& name = arr[0].name();
+  const BluetoothValue& value = arr[0].value();
+
+  if (name.EqualsLiteral("Connected")) {
+    MOZ_ASSERT(value.type() == BluetoothValue::Tbool);
+    MOZ_ASSERT(mConnected != value.get_bool());
+
+    mConnected = value.get_bool();
+    NotifyStatusChanged();
+  }
+}
+
+void
+BluetoothHidManager::NotifyStatusChanged()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_NAMED_LITERAL_STRING(type, BLUETOOTH_HID_STATUS_CHANGED_ID);
+  InfallibleTArray<BluetoothNamedValue> parameters;
+
+  BluetoothValue v = mConnected;
+  parameters.AppendElement(
+    BluetoothNamedValue(NS_LITERAL_STRING("connected"), v));
+
+  v = mDeviceAddress;
+  parameters.AppendElement(
+    BluetoothNamedValue(NS_LITERAL_STRING("address"), v));
+
+  if (!BroadcastSystemMessage(type, parameters)) {
+    NS_WARNING("Failed to broadcast system message to settings");
+    return;
+  }
+}
+
+void
+BluetoothHidManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                         const nsAString& aServiceUuid,
+                                         int aChannel)
+{
+  // Do nothing here as bluez acquires service channel and connects for us
+}
+
+void
+BluetoothHidManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
+{
+  // Do nothing here as bluez acquires service channel and connects for us
+}
+
+void
+BluetoothHidManager::GetAddress(nsAString& aDeviceAddress)
+{
+  aDeviceAddress = mDeviceAddress;
+}
+
+NS_IMPL_ISUPPORTS1(BluetoothHidManager, nsIObserver)
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/BluetoothHidManager.h
@@ -0,0 +1,53 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothhidmanager_h__
+#define mozilla_dom_bluetooth_bluetoothhidmanager_h__
+
+#include "BluetoothCommon.h"
+#include "BluetoothProfileManagerBase.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothReplyRunnable;
+
+class BluetoothHidManager : public BluetoothProfileManagerBase
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static BluetoothHidManager* Get();
+  ~BluetoothHidManager();
+
+  virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                   const nsAString& aServiceUuid,
+                                   int aChannel) MOZ_OVERRIDE;
+  virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) MOZ_OVERRIDE;
+  virtual void GetAddress(nsAString& aDeviceAddress) MOZ_OVERRIDE;
+  virtual bool IsConnected() MOZ_OVERRIDE;
+
+  bool Connect(const nsAString& aDeviceAddress,
+               BluetoothReplyRunnable* aRunnable);
+  void Disconnect();
+  void HandleInputPropertyChanged(const BluetoothSignal& aSignal);
+
+private:
+  BluetoothHidManager();
+  bool Init();
+  void Cleanup();
+  void HandleShutdown();
+
+  void NotifyStatusChanged();
+
+  // data member
+  bool mConnected;
+  nsString mDeviceAddress;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif //#ifndef mozilla_dom_bluetooth_bluetoothhidmanager_h__
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -289,16 +289,21 @@ public:
   UpdatePlayStatus(uint32_t aDuration,
                    uint32_t aPosition,
                    ControlPlayStatus aPlayStatus) = 0;
 
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) = 0;
 
+  virtual nsresult
+  SendInputMessage(const nsAString& aDeviceAddresses,
+                   const nsAString& aMessage,
+                   BluetoothReplyRunnable* aRunnable) = 0;
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
--- a/dom/bluetooth/BluetoothUuid.h
+++ b/dom/bluetooth/BluetoothUuid.h
@@ -21,17 +21,18 @@ BEGIN_BLUETOOTH_NAMESPACE
  * Bluetooth Core Specification.
  */
 enum BluetoothServiceClass
 {
   HEADSET       = 0x1108,
   HEADSET_AG    = 0x1112,
   HANDSFREE     = 0x111E,
   HANDSFREE_AG  = 0x111F,
-  OBJECT_PUSH   = 0x1105
+  OBJECT_PUSH   = 0x1105,
+  HID           = 0x1124,
 };
 
 class BluetoothUuidHelper
 {
 public:
   /**
    * Get a 128-bit uuid string calculated from a 16-bit service class UUID and
    * BASE_UUID
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -410,16 +410,24 @@ BluetoothServiceChildProcess::IsConnecte
 
 nsresult
 BluetoothServiceChildProcess::SendSinkMessage(const nsAString& aDeviceAddresses,
                                               const nsAString& aMessage)
 {
   MOZ_CRASH("This should never be called!");
 }
 
+nsresult
+BluetoothServiceChildProcess::SendInputMessage(const nsAString& aDeviceAddresses,
+                                               const nsAString& aMessage,
+                                               BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_CRASH("This should never be called!");
+}
+
 void
 BluetoothServiceChildProcess::UpdatePlayStatus(uint32_t aDuration,
                                                uint32_t aPosition,
                                                ControlPlayStatus aPlayStatus)
 {
   MOZ_CRASH("This should never be called!");
 }
 
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -171,16 +171,21 @@ public:
   UpdatePlayStatus(uint32_t aDuration,
                    uint32_t aPosition,
                    ControlPlayStatus aPlayStatus) MOZ_OVERRIDE;
 
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
+  virtual nsresult
+  SendInputMessage(const nsAString& aDeviceAddresses,
+                   const nsAString& aMessage,
+                   BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -15,16 +15,17 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 
 #include "base/basictypes.h"
 #include "BluetoothDBusService.h"
 #include "BluetoothA2dpManager.h"
 #include "BluetoothHfpManager.h"
+#include "BluetoothHidManager.h"
 #include "BluetoothOppManager.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothUnixSocketConnector.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 
 #include <cstdio>
 #include <dbus/dbus.h>
@@ -64,16 +65,17 @@ USING_BLUETOOTH_NAMESPACE
 
 #define B2G_AGENT_CAPABILITIES "DisplayYesNo"
 #define DBUS_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC  ".Manager"
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC  ".Adapter"
 #define DBUS_DEVICE_IFACE  BLUEZ_DBUS_BASE_IFC  ".Device"
 #define DBUS_AGENT_IFACE   BLUEZ_DBUS_BASE_IFC  ".Agent"
 #define DBUS_SINK_IFACE    BLUEZ_DBUS_BASE_IFC  ".AudioSink"
 #define DBUS_CTL_IFACE     BLUEZ_DBUS_BASE_IFC  ".Control"
+#define DBUS_INPUT_IFACE   BLUEZ_DBUS_BASE_IFC  ".Input"
 #define BLUEZ_DBUS_BASE_PATH      "/org/bluez"
 #define BLUEZ_DBUS_BASE_IFC       "org.bluez"
 #define BLUEZ_ERROR_IFC           "org.bluez.Error"
 
 #define ERR_A2DP_IS_DISCONNECTED      "A2dpIsDisconnected"
 #define ERR_AVRCP_IS_DISCONNECTED     "AvrcpIsDisconnected"
 #define ERR_UNKNOWN_PROFILE           "UnknownProfileError"
 
@@ -134,16 +136,20 @@ static Properties sSinkProperties[] = {
   {"Connected", DBUS_TYPE_BOOLEAN},
   {"Playing", DBUS_TYPE_BOOLEAN}
 };
 
 static Properties sControlProperties[] = {
   {"Connected", DBUS_TYPE_BOOLEAN}
 };
 
+static Properties sInputProperties[] = {
+  {"Connected", DBUS_TYPE_BOOLEAN}
+};
+
 static const char* sBluetoothDBusIfaces[] =
 {
   DBUS_MANAGER_IFACE,
   DBUS_ADAPTER_IFACE,
   DBUS_DEVICE_IFACE
 };
 
 static const char* sBluetoothDBusSignals[] =
@@ -170,17 +176,16 @@ static nsDataHashtable<nsStringHashKey, 
 static nsDataHashtable<nsStringHashKey, DBusMessage* > sAuthorizeReqTable;
 static nsString sAdapterPath;
 static Atomic<int32_t> sIsPairing(0);
 static int sConnectedDeviceCount = 0;
 static Monitor sStopBluetoothMonitor("BluetoothService.sStopBluetoothMonitor");
 
 typedef void (*UnpackFunc)(DBusMessage*, DBusError*, BluetoothValue&, nsAString&);
 typedef bool (*FilterFunc)(const BluetoothValue&);
-typedef void (*SinkCallback)(DBusMessage*, void*);
 
 static bool
 GetConnectedDevicesFilter(const BluetoothValue& aValue)
 {
   // We don't have to filter device here
   return true;
 }
 
@@ -285,16 +290,45 @@ public:
     a2dp->HandleSinkPropertyChanged(mSignal);
     return NS_OK;
   }
 
 private:
   BluetoothSignal mSignal;
 };
 
+class InputPropertyChangedHandler : public nsRunnable
+{
+public:
+  InputPropertyChangedHandler(const BluetoothSignal& aSignal)
+    : mSignal(aSignal)
+  {
+  }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mSignal.name().EqualsLiteral("PropertyChanged"));
+    MOZ_ASSERT(mSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+    // Replace object path with device address
+    nsString address = GetAddressFromObjectPath(mSignal.path());
+    mSignal.path() = address;
+
+    BluetoothHidManager* hid = BluetoothHidManager::Get();
+    NS_ENSURE_TRUE(hid, NS_ERROR_FAILURE);
+    hid->HandleInputPropertyChanged(mSignal);
+    return NS_OK;
+  }
+
+private:
+  BluetoothSignal mSignal;
+};
+
 static bool
 IsDBusMessageError(DBusMessage* aMsg, DBusError* aErr, nsAString& aErrorStr)
 {
   if (aErr && dbus_error_is_set(aErr)) {
     aErrorStr = NS_ConvertUTF8toUTF16(aErr->message);
     LOG_AND_FREE_DBUS_ERROR(aErr);
     return true;
   }
@@ -485,30 +519,39 @@ CheckForError(DBusMessage* aMsg, void *a
   UnpackVoidMessage(aMsg, nullptr, v, replyError);
   if (!v.get_bool()) {
     BT_WARNING(NS_ConvertUTF16toUTF8(aError).get());
   }
 }
 #endif
 
 static void
+InputDisconnectCallback(DBusMessage* aMsg, void* aParam)
+{
+#ifdef DEBUG
+  NS_NAMED_LITERAL_STRING(errorStr, "Failed to disconnect input device");
+  CheckForError(aMsg, aParam, errorStr);
+#endif
+}
+
+static void
 SinkConnectCallback(DBusMessage* aMsg, void* aParam)
 {
 #ifdef DEBUG
   NS_NAMED_LITERAL_STRING(errorStr, "Failed to connect sink");
   CheckForError(aMsg, aParam, errorStr);
 #endif
 }
 
 static void
 SinkDisconnectCallback(DBusMessage* aMsg, void* aParam)
 {
 #ifdef DEBUG
   NS_NAMED_LITERAL_STRING(errorStr, "Failed to disconnect sink");
-  CheckForError(false, aMsg, errorStr);
+  CheckForError(aMsg, aParam, errorStr);
 #endif
 }
 
 static bool
 HasAudioService(uint32_t aCodValue)
 {
   return ((aCodValue & 0x200000) == 0x200000);
 }
@@ -1553,32 +1596,41 @@ EventFilter(DBusConnection* aConn, DBusM
     NS_DispatchToMainThread(new SendPlayStatusTask());
     return DBUS_HANDLER_RESULT_HANDLED;
   } else if (dbus_message_is_signal(aMsg, DBUS_CTL_IFACE, "PropertyChanged")) {
     ParsePropertyChange(aMsg,
                         v,
                         errorStr,
                         sControlProperties,
                         ArrayLength(sControlProperties));
+  } else if (dbus_message_is_signal(aMsg, DBUS_INPUT_IFACE,
+                                    "PropertyChanged")) {
+    ParsePropertyChange(aMsg,
+                        v,
+                        errorStr,
+                        sInputProperties,
+                        ArrayLength(sInputProperties));
   } else {
     errorStr = NS_ConvertUTF8toUTF16(dbus_message_get_member(aMsg));
     errorStr.AppendLiteral(" Signal not handled!");
   }
 
   if (!errorStr.IsEmpty()) {
     NS_WARNING(NS_ConvertUTF16toUTF8(errorStr).get());
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   }
 
   BluetoothSignal signal(signalName, signalPath, v);
   nsRefPtr<nsRunnable> task;
   if (signalInterface.EqualsLiteral(DBUS_SINK_IFACE)) {
     task = new SinkPropertyChangedHandler(signal);
   } else if (signalInterface.EqualsLiteral(DBUS_CTL_IFACE)) {
     task = new ControlPropertyChangedHandler(signal);
+  } else if (signalInterface.EqualsLiteral(DBUS_INPUT_IFACE)) {
+    task = new InputPropertyChangedHandler(signal);
   } else {
     task = new DistributeBluetoothSignalTask(signal);
   }
 
   NS_DispatchToMainThread(task);
 
   return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -1878,24 +1930,61 @@ BluetoothDBusService::SendDiscoveryMessa
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 
   runnable.forget();
 
   return NS_OK;
 }
 
 nsresult
+BluetoothDBusService::SendInputMessage(const nsAString& aDeviceAddress,
+                                       const nsAString& aMessage,
+                                       BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mConnection);
+  MOZ_ASSERT(aMessage.EqualsLiteral("Connect") ||
+             aMessage.EqualsLiteral("Disconnect"));
+
+  NS_ENSURE_TRUE(IsReady(), NS_ERROR_FAILURE);
+
+  DBusCallback callback;
+  if (aMessage.EqualsLiteral("Connect")) {
+    callback = GetVoidCallback;
+  } else if (aMessage.EqualsLiteral("Disconnect")) {
+    callback = InputDisconnectCallback;
+  }
+
+  nsRefPtr<BluetoothReplyRunnable> runnable(aRunnable);
+
+  nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress);
+  bool ret = dbus_func_args_async(mConnection,
+                                  -1,
+                                  callback,
+                                  static_cast<void*>(runnable.get()),
+                                  NS_ConvertUTF16toUTF8(objectPath).get(),
+                                  DBUS_INPUT_IFACE,
+                                  NS_ConvertUTF16toUTF8(aMessage).get(),
+                                  DBUS_TYPE_INVALID);
+  NS_ENSURE_TRUE(ret, NS_ERROR_FAILURE);
+
+  runnable.forget();
+
+  return NS_OK;
+}
+
+nsresult
 BluetoothDBusService::SendSinkMessage(const nsAString& aDeviceAddress,
                                       const nsAString& aMessage)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(IsEnabled());
 
-  SinkCallback callback;
+  DBusCallback callback;
   if (aMessage.EqualsLiteral("Connect")) {
     callback = SinkConnectCallback;
   } else if (aMessage.EqualsLiteral("Disconnect")) {
     callback = SinkDisconnectCallback;
   } else {
     BT_WARNING("Unknown sink message");
     return NS_ERROR_FAILURE;
   }
@@ -1904,18 +1993,18 @@ BluetoothDBusService::SendSinkMessage(co
   bool ret = dbus_func_args_async(mConnection,
                                   -1,
                                   callback,
                                   nullptr,
                                   NS_ConvertUTF16toUTF8(objectPath).get(),
                                   DBUS_SINK_IFACE,
                                   NS_ConvertUTF16toUTF8(aMessage).get(),
                                   DBUS_TYPE_INVALID);
-
   NS_ENSURE_TRUE(ret, NS_ERROR_FAILURE);
+
   return NS_OK;
 }
 
 nsresult
 BluetoothDBusService::StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable)
 {
   return SendDiscoveryMessage("StopDiscovery", aRunnable);
 }
@@ -2017,16 +2106,18 @@ BluetoothDBusService::GetConnectedDevice
     return NS_OK;
   }
 
   nsTArray<nsString> deviceAddresses;
   BluetoothProfileManagerBase* profile;
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
     profile = BluetoothHfpManager::Get();
+  } else if (aProfileId == BluetoothServiceClass::HID) {
+    profile = BluetoothHidManager::Get();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     profile = BluetoothOppManager::Get();
   } else {
     DispatchBluetoothReply(aRunnable, values,
                            NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
     return NS_OK;
   }
 
@@ -2484,16 +2575,19 @@ BluetoothDBusService::Connect(const nsAS
   MOZ_ASSERT(NS_IsMainThread());
 
   if (aProfileId == BluetoothServiceClass::HANDSFREE) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Connect(aDeviceAddress, true, aRunnable);
   } else if (aProfileId == BluetoothServiceClass::HEADSET) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Connect(aDeviceAddress, false, aRunnable);
+  } else if (aProfileId == BluetoothServiceClass::HID) {
+    BluetoothHidManager* hid = BluetoothHidManager::Get();
+    hid->Connect(aDeviceAddress, aRunnable);
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     opp->Connect(aDeviceAddress, aRunnable);
   } else {
     DispatchBluetoothReply(aRunnable, BluetoothValue(),
                            NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
   }
 }
@@ -2503,16 +2597,19 @@ BluetoothDBusService::Disconnect(const u
                                  BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Disconnect();
+  } else if (aProfileId == BluetoothServiceClass::HID) {
+    BluetoothHidManager* hid = BluetoothHidManager::Get();
+    hid->Disconnect();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     opp->Disconnect();
   } else {
     BT_WARNING(ERR_UNKNOWN_PROFILE);
     return;
   }
 
@@ -2526,16 +2623,18 @@ bool
 BluetoothDBusService::IsConnected(const uint16_t aProfileId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothProfileManagerBase* profile;
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
     profile = BluetoothHfpManager::Get();
+  } else if (aProfileId == BluetoothServiceClass::HID) {
+    profile = BluetoothHidManager::Get();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     profile = BluetoothOppManager::Get();
   } else {
     NS_WARNING(ERR_UNKNOWN_PROFILE);
     return false;
   }
 
   NS_ENSURE_TRUE(profile, false);
--- a/dom/bluetooth/linux/BluetoothDBusService.h
+++ b/dom/bluetooth/linux/BluetoothDBusService.h
@@ -157,16 +157,21 @@ public:
   UpdatePlayStatus(uint32_t aDuration,
                    uint32_t aPosition,
                    ControlPlayStatus aPlayStatus) MOZ_OVERRIDE;
 
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
+  virtual nsresult
+  SendInputMessage(const nsAString& aDeviceAddresses,
+                   const nsAString& aMessage,
+                   BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
 private:
   /**
    * For DBus Control method of "UpdateNotification", event id should be
    * specified as following:
    * (Please see specification of AVRCP 1.3, Table 5.28 for more details.)
    */
   enum ControlEventId {
     EVENT_PLAYBACK_STATUS_CHANGED            = 0x01,
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -32,16 +32,17 @@ if CONFIG['MOZ_B2G_BT']:
         'BluetoothPropertyContainer.cpp',
         'BluetoothUtils.cpp',
         'BluetoothChild.cpp',
         'BluetoothParent.cpp',
         'BluetoothServiceChildProcess.cpp',
         'BluetoothUnixSocketConnector.cpp',
         'BluetoothA2dpManager.cpp',
         'BluetoothHfpManager.cpp',
+        'BluetoothHidManager.cpp',
         'BluetoothOppManager.cpp',
         'ObexBase.cpp',
         'BluetoothUuid.cpp',
         'BluetoothSocket.cpp',
     ]
 
     if CONFIG['MOZ_B2G_RIL']:
         CPP_SOURCES += [
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -44,16 +44,19 @@ this.SystemMessagePermissionsTable = {
     "bluetooth": []
   },
   "bluetooth-a2dp-status-changed": {
     "bluetooth": []
   },
   "bluetooth-hfp-status-changed": {
     "bluetooth": []
   },
+  "bluetooth-hid-status-changed": {
+    "bluetooth": []
+  },
   "bluetooth-sco-status-changed": {
     "bluetooth": []
   },
   "bluetooth-pairing-request": {
     "bluetooth": []
   },
   "bluetooth-opp-transfer-complete": {
     "bluetooth": []