diff --git a/dom/bluetooth/BluetoothAdapter.cpp b/dom/bluetooth/BluetoothAdapter.cpp index 7d56a2f2896..a86b28b97a9 100644 --- a/dom/bluetooth/BluetoothAdapter.cpp +++ b/dom/bluetooth/BluetoothAdapter.cpp @@ -17,6 +17,7 @@ #include "nsDOMClassInfo.h" #include "nsIDOMBluetoothDeviceEvent.h" #include "nsTArrayHelpers.h" +#include "DictionaryHelpers.h" #include "DOMRequest.h" #include "nsThreadUtils.h" @@ -833,4 +834,71 @@ BluetoothAdapter::IsScoConnected(nsIDOMDOMRequest** aRequest) return NS_OK; } +NS_IMETHODIMP +BluetoothAdapter::SendMediaMetaData(const JS::Value& aOptions, + nsIDOMDOMRequest** aRequest) +{ + idl::MediaMetaData metadata; + + nsresult rv; + nsIScriptContext* sc = GetContextForEventHandlers(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + AutoPushJSContext cx(sc->GetNativeContext()); + rv = metadata.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr req; + rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr results = + new BluetoothVoidReplyRunnable(req); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); + bs->SendMetaData(metadata.title, + metadata.artist, + metadata.album, + metadata.mediaNumber, + metadata.totalMediaCount, + metadata.duration, + results); + + req.forget(aRequest); + return NS_OK; +} + +NS_IMETHODIMP +BluetoothAdapter::SendMediaPlayStatus(const JS::Value& aOptions, + nsIDOMDOMRequest** aRequest) +{ + idl::MediaPlayStatus status; + + nsresult rv; + nsIScriptContext* sc = GetContextForEventHandlers(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + AutoPushJSContext cx(sc->GetNativeContext()); + rv = status.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr req; + rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr results = + new BluetoothVoidReplyRunnable(req); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); + bs->SendPlayStatus(status.duration, + status.position, + status.playStatus, + results); + + req.forget(aRequest); + return NS_OK; +} + NS_IMPL_EVENT_HANDLER(BluetoothAdapter, devicefound) diff --git a/dom/bluetooth/BluetoothCommon.h b/dom/bluetooth/BluetoothCommon.h index 8d890b912e2..16001ad2eb9 100644 --- a/dom/bluetooth/BluetoothCommon.h +++ b/dom/bluetooth/BluetoothCommon.h @@ -85,6 +85,16 @@ enum BluetoothObjectType { TYPE_INVALID }; +enum ControlPlayStatus { + PLAYSTATUS_STOPPED = 0x00, + PLAYSTATUS_PLAYING = 0x01, + PLAYSTATUS_PAUSED = 0x02, + PLAYSTATUS_FWD_SEEK = 0x03, + PLAYSTATUS_REV_SEEK = 0x04, + PLAYSTATUS_UNKNOWN, + PLAYSTATUS_ERROR = 0xFF, +}; + END_BLUETOOTH_NAMESPACE #endif // mozilla_dom_bluetooth_bluetoothcommon_h__ diff --git a/dom/bluetooth/BluetoothService.h b/dom/bluetooth/BluetoothService.h index cc5e84b78ae..9999a743f22 100644 --- a/dom/bluetooth/BluetoothService.h +++ b/dom/bluetooth/BluetoothService.h @@ -270,6 +270,21 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) = 0; + virtual void + SendMetaData(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum, + uint32_t aMediaNumber, + uint32_t aTotalMediaCount, + uint32_t aDuration, + BluetoothReplyRunnable* aRunnable) = 0; + + virtual void + SendPlayStatus(uint32_t aDuration, + uint32_t aPosition, + const nsAString& aPlayStatus, + BluetoothReplyRunnable* aRunnable) = 0; + virtual nsresult SendSinkMessage(const nsAString& aDeviceAddresses, const nsAString& aMessage) = 0; diff --git a/dom/bluetooth/ipc/BluetoothParent.cpp b/dom/bluetooth/ipc/BluetoothParent.cpp index 4fdadc1ba82..23a0babb7e7 100644 --- a/dom/bluetooth/ipc/BluetoothParent.cpp +++ b/dom/bluetooth/ipc/BluetoothParent.cpp @@ -230,6 +230,10 @@ BluetoothParent::RecvPBluetoothRequestConstructor( return actor->DoRequest(aRequest.get_DisconnectScoRequest()); case Request::TIsScoConnectedRequest: return actor->DoRequest(aRequest.get_IsScoConnectedRequest()); + case Request::TSendMetaDataRequest: + return actor->DoRequest(aRequest.get_SendMetaDataRequest()); + case Request::TSendPlayStatusRequest: + return actor->DoRequest(aRequest.get_SendPlayStatusRequest()); default: MOZ_CRASH("Unknown type!"); } @@ -603,3 +607,32 @@ BluetoothRequestParent::DoRequest(const IsScoConnectedRequest& aRequest) mService->IsScoConnected(mReplyRunnable.get()); return true; } + +bool +BluetoothRequestParent::DoRequest(const SendMetaDataRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TSendMetaDataRequest); + + mService->SendMetaData(aRequest.title(), + aRequest.artist(), + aRequest.album(), + aRequest.mediaNumber(), + aRequest.totalMediaCount(), + aRequest.duration(), + mReplyRunnable.get()); + return true; +} + +bool +BluetoothRequestParent::DoRequest(const SendPlayStatusRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TSendPlayStatusRequest); + + mService->SendPlayStatus(aRequest.duration(), + aRequest.position(), + aRequest.playStatus(), + mReplyRunnable.get()); + return true; +} diff --git a/dom/bluetooth/ipc/BluetoothParent.h b/dom/bluetooth/ipc/BluetoothParent.h index 460898243c4..633958f8ba3 100644 --- a/dom/bluetooth/ipc/BluetoothParent.h +++ b/dom/bluetooth/ipc/BluetoothParent.h @@ -194,6 +194,12 @@ protected: bool DoRequest(const IsScoConnectedRequest& aRequest); + + bool + DoRequest(const SendMetaDataRequest& aRequest); + + bool + DoRequest(const SendPlayStatusRequest& aRequest); }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp index 03a04a961b2..982766cc658 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp @@ -339,6 +339,32 @@ BluetoothServiceChildProcess::IsScoConnected(BluetoothReplyRunnable* aRunnable) SendRequest(aRunnable, IsScoConnectedRequest()); } +void +BluetoothServiceChildProcess::SendMetaData(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum, + uint32_t aMediaNumber, + uint32_t aTotalMediaCount, + uint32_t aDuration, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, + SendMetaDataRequest(nsString(aTitle), nsString(aArtist), + nsString(aAlbum), aMediaNumber, + aTotalMediaCount, aDuration)); +} + +void +BluetoothServiceChildProcess::SendPlayStatus(uint32_t aDuration, + uint32_t aPosition, + const nsAString& aPlayStatus, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, + SendPlayStatusRequest(aDuration, aPosition, + nsString(aPlayStatus))); +} + nsresult BluetoothServiceChildProcess::HandleStartup() { @@ -388,3 +414,4 @@ BluetoothServiceChildProcess::SendSinkMessage(const nsAString& aDeviceAddresses, { MOZ_CRASH("This should never be called!"); } + diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h index e732d96808f..2d472a4b986 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h @@ -152,6 +152,21 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + virtual void + SendMetaData(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum, + uint32_t aMediaNumber, + uint32_t aTotalMediaCount, + uint32_t aDuration, + BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + + virtual void + SendPlayStatus(uint32_t aDuration, + uint32_t aPosition, + const nsAString& aPlayStatus, + BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + virtual nsresult SendSinkMessage(const nsAString& aDeviceAddresses, const nsAString& aMessage) MOZ_OVERRIDE; diff --git a/dom/bluetooth/ipc/PBluetooth.ipdl b/dom/bluetooth/ipc/PBluetooth.ipdl index 1eb955cd55f..ecf8bd506ef 100644 --- a/dom/bluetooth/ipc/PBluetooth.ipdl +++ b/dom/bluetooth/ipc/PBluetooth.ipdl @@ -141,6 +141,23 @@ struct IsScoConnectedRequest { }; +struct SendMetaDataRequest +{ + nsString title; + nsString artist; + nsString album; + uint32_t mediaNumber; + uint32_t totalMediaCount; + uint32_t duration; +}; + +struct SendPlayStatusRequest +{ + uint32_t duration; + uint32_t position; + nsString playStatus; +}; + union Request { DefaultAdapterPathRequest; @@ -167,6 +184,8 @@ union Request ConnectScoRequest; DisconnectScoRequest; IsScoConnectedRequest; + SendMetaDataRequest; + SendPlayStatusRequest; }; protocol PBluetooth diff --git a/dom/bluetooth/linux/BluetoothDBusService.cpp b/dom/bluetooth/linux/BluetoothDBusService.cpp index e4a18d4dbaf..58782b01b2c 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.cpp +++ b/dom/bluetooth/linux/BluetoothDBusService.cpp @@ -63,14 +63,16 @@ 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_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 BLUEZ_DBUS_BASE_PATH "/org/bluez" #define BLUEZ_DBUS_BASE_IFC "org.bluez" #define BLUEZ_ERROR_IFC "org.bluez.Error" -#define PROP_DEVICE_CONNECTED_TYPE "org.bluez.device.conn.type" +#define ERR_A2DP_IS_DISCONNECTED "A2dpIsDisconnected" +#define ERR_AVRCP_IS_DISCONNECTED "AvrcpIsDisconnected" typedef struct { const char* name; @@ -123,6 +125,10 @@ static Properties sSinkProperties[] = { {"Playing", DBUS_TYPE_BOOLEAN} }; +static Properties sControlProperties[] = { + {"Connected", DBUS_TYPE_BOOLEAN} +}; + static const char* sBluetoothDBusIfaces[] = { DBUS_MANAGER_IFACE, @@ -140,7 +146,8 @@ static const char* sBluetoothDBusSignals[] = "type='signal',interface='org.bluez.Network'", "type='signal',interface='org.bluez.NetworkServer'", "type='signal',interface='org.bluez.HealthDevice'", - "type='signal',interface='org.bluez.AudioSink'" + "type='signal',interface='org.bluez.AudioSink'", + "type='signal',interface='org.bluez.Control'" }; /** @@ -2802,3 +2809,134 @@ BluetoothDBusService::IsScoConnected(BluetoothReplyRunnable* aRunnable) hfp->IsScoConnected(), EmptyString()); } +void +BluetoothDBusService::SendMetaData(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum, + uint32_t aMediaNumber, + uint32_t aTotalMediaCount, + uint32_t aDuration, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsReady()) { + NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); + DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); + return; + } + + BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); + NS_ENSURE_TRUE_VOID(a2dp); + + nsAutoString address; + a2dp->GetAddress(address); + nsString objectPath = + GetObjectPathFromAddress(sAdapterPath, address); + + nsCString tempTitle = NS_ConvertUTF16toUTF8(aTitle); + nsCString tempArtist = NS_ConvertUTF16toUTF8(aArtist); + nsCString tempAlbum = NS_ConvertUTF16toUTF8(aAlbum); + nsCString tempMediaNumber, tempTotalMediaCount, tempDuration; + tempMediaNumber.AppendInt(aMediaNumber); + tempTotalMediaCount.AppendInt(aTotalMediaCount); + tempDuration.AppendInt(aDuration); + + const char* title = tempTitle.get(); + const char* album = tempAlbum.get(); + const char* artist = tempArtist.get(); + const char* mediaNumber = tempMediaNumber.get(); + const char* totalMediaCount = tempTotalMediaCount.get(); + const char* duration = tempDuration.get(); + + nsRefPtr runnable(aRunnable); + + bool ret = dbus_func_args_async(mConnection, + -1, + GetVoidCallback, + (void*)runnable.get(), + NS_ConvertUTF16toUTF8(objectPath).get(), + DBUS_CTL_IFACE, + "UpdateMetaData", + DBUS_TYPE_STRING, &title, + DBUS_TYPE_STRING, &artist, + DBUS_TYPE_STRING, &album, + DBUS_TYPE_STRING, &mediaNumber, + DBUS_TYPE_STRING, &totalMediaCount, + DBUS_TYPE_STRING, &duration, + DBUS_TYPE_INVALID); + NS_ENSURE_TRUE_VOID(ret); + + runnable.forget(); +} + +static ControlPlayStatus +PlayStatusStringToControlPlayStatus(const nsAString& aPlayStatus) +{ + ControlPlayStatus playStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; + if (aPlayStatus.EqualsLiteral("STOPPED")) { + playStatus = ControlPlayStatus::PLAYSTATUS_STOPPED; + } else if (aPlayStatus.EqualsLiteral("PLAYING")) { + playStatus = ControlPlayStatus::PLAYSTATUS_PLAYING; + } else if (aPlayStatus.EqualsLiteral("PAUSED")) { + playStatus = ControlPlayStatus::PLAYSTATUS_PAUSED; + } else if (aPlayStatus.EqualsLiteral("FWD_SEEK")) { + playStatus = ControlPlayStatus::PLAYSTATUS_FWD_SEEK; + } else if (aPlayStatus.EqualsLiteral("REV_SEEK")) { + playStatus = ControlPlayStatus::PLAYSTATUS_REV_SEEK; + } else if (aPlayStatus.EqualsLiteral("ERROR")) { + playStatus = ControlPlayStatus::PLAYSTATUS_ERROR; + } + + return playStatus; +} + +void +BluetoothDBusService::SendPlayStatus(uint32_t aDuration, + uint32_t aPosition, + const nsAString& aPlayStatus, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsReady()) { + NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); + DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); + return; + } + + ControlPlayStatus playStatus = + PlayStatusStringToControlPlayStatus(aPlayStatus); + if (playStatus == ControlPlayStatus::PLAYSTATUS_UNKNOWN) { + DispatchBluetoothReply(aRunnable, BluetoothValue(), + NS_LITERAL_STRING("Invalid play status")); + return; + } + + BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); + NS_ENSURE_TRUE_VOID(a2dp); + + nsAutoString address; + a2dp->GetAddress(address); + nsString objectPath = + GetObjectPathFromAddress(sAdapterPath, address); + + nsRefPtr runnable(aRunnable); + + uint32_t tempPlayStatus = playStatus; + bool ret = dbus_func_args_async(mConnection, + -1, + GetVoidCallback, + (void*)runnable.get(), + NS_ConvertUTF16toUTF8(objectPath).get(), + DBUS_CTL_IFACE, + "UpdatePlayStatus", + DBUS_TYPE_UINT32, &aDuration, + DBUS_TYPE_UINT32, &aPosition, + DBUS_TYPE_UINT32, &tempPlayStatus, + DBUS_TYPE_INVALID); + NS_ENSURE_TRUE_VOID(ret); + + runnable.forget(); +} + diff --git a/dom/bluetooth/linux/BluetoothDBusService.h b/dom/bluetooth/linux/BluetoothDBusService.h index 118edd94203..d3f9144835b 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.h +++ b/dom/bluetooth/linux/BluetoothDBusService.h @@ -138,6 +138,21 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + virtual void + SendMetaData(const nsAString& aTitle, + const nsAString& aArtist, + const nsAString& aAlbum, + uint32_t aMediaNumber, + uint32_t aTotalMediaCount, + uint32_t aDuration, + BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + + virtual void + SendPlayStatus(uint32_t aDuration, + uint32_t aPosition, + const nsAString& aPlayStatus, + BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE; + virtual nsresult SendSinkMessage(const nsAString& aDeviceAddresses, const nsAString& aMessage) MOZ_OVERRIDE; @@ -147,13 +162,16 @@ private: const char* aInterface, void (*aCB)(DBusMessage *, void *), BluetoothReplyRunnable* aRunnable); + nsresult SendDiscoveryMessage(const char* aMessageName, BluetoothReplyRunnable* aRunnable); + nsresult SendSetPropertyMessage(const char* aInterface, const BluetoothNamedValue& aValue, BluetoothReplyRunnable* aRunnable); void DisconnectAllAcls(const nsAString& aAdapterPath); + }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/nsIDOMBluetoothAdapter.idl b/dom/bluetooth/nsIDOMBluetoothAdapter.idl index fc5625ea4cc..6c8edcd5c38 100644 --- a/dom/bluetooth/nsIDOMBluetoothAdapter.idl +++ b/dom/bluetooth/nsIDOMBluetoothAdapter.idl @@ -6,11 +6,44 @@ #include "nsIDOMEventTarget.idl" +/** + * MediaMetadata and MediaPlayStatus are used to keep data from Applications. + * Please see specification of AVRCP 1.3 for more details. + * + * @title: track title + * @artist: artist name + * @album: album name + * @mediaNumber: track number + * @totalMediaCount: number of tracks in the album + * @duration: playing time (ms) + */ +dictionary MediaMetaData +{ + DOMString title; + DOMString artist; + DOMString album; + unsigned long mediaNumber; + unsigned long totalMediaCount; + unsigned long duration; +}; + +/** + * @duration: current track length (ms) + * @position: playing time (ms) + * @playStatus: STOPPED/PLAYING/PAUSED/FWD_SEEK/REV_SEEK/ERROR + */ +dictionary MediaPlayStatus +{ + unsigned long duration; + unsigned long position; + DOMString playStatus; +}; + interface nsIDOMDOMRequest; interface nsIDOMBlob; interface nsIDOMBluetoothDevice; -[scriptable, builtinclass, uuid(7058d214-3575-4913-99ad-0980296f617a)] +[scriptable, builtinclass, uuid(1a0c6c90-23e3-4f4c-8076-98e4341c2024)] interface nsIDOMBluetoothAdapter : nsIDOMEventTarget { readonly attribute DOMString address; @@ -58,6 +91,10 @@ interface nsIDOMBluetoothAdapter : nsIDOMEventTarget nsIDOMDOMRequest stopSendingFile(in DOMString aDeviceAddress); nsIDOMDOMRequest confirmReceivingFile(in DOMString aDeviceAddress, in bool aConfirmation); + // AVRCP 1.3 methods + nsIDOMDOMRequest sendMediaMetaData(in jsval aOptions); + nsIDOMDOMRequest sendMediaPlayStatus(in jsval aOptions); + // Connect/Disconnect SCO (audio) connection nsIDOMDOMRequest connectSco(); nsIDOMDOMRequest disconnectSco(); diff --git a/js/xpconnect/src/dictionary_helper_gen.conf b/js/xpconnect/src/dictionary_helper_gen.conf index fefb520e939..243705fdde1 100644 --- a/js/xpconnect/src/dictionary_helper_gen.conf +++ b/js/xpconnect/src/dictionary_helper_gen.conf @@ -13,7 +13,9 @@ dictionaries = [ [ 'CameraSelector', 'nsIDOMCameraManager.idl' ], [ 'CameraRecordingOptions', 'nsIDOMCameraManager.idl' ], [ 'SmsThreadListItem', 'nsIMobileMessageCallback.idl' ], - [ 'MmsAttachment', 'nsIDOMMozMmsMessage.idl' ] + [ 'MmsAttachment', 'nsIDOMMozMmsMessage.idl' ], + [ 'MediaMetaData', 'nsIDOMBluetoothAdapter.idl'], + [ 'MediaPlayStatus', 'nsIDOMBluetoothAdapter.idl'] ] # include file names