From patchwork Sat Oct 5 21:43:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marijn Suijten X-Patchwork-Id: 13823595 Received: from relay02.th.seeweb.it (relay02.th.seeweb.it [5.144.164.163]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 20114171088 for ; Sat, 5 Oct 2024 21:50:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=5.144.164.163 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; cv=none; b=IIA42YAGgt7UIhJxjOJTnw6JZ5wb38XY8Z9subR+bev87t17179eRTIzoIOIhtkJ+bG1j9VZNCY5slrMj7B9w/GZiRDPI+uNuztXBGBCiyRNppvkmAA8N2IPpgaWrsIY0jAAhCTDYeHV85u5Sjt6ptwxiAm5MFkPuSK0vFRHsB0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; c=relaxed/simple; bh=NQ8oHyAK5DK5N2JIoQO0q21RhpsMba9BN8GC+sW2UIg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bR4fgfF4kfjzphnB+ZhUrymSHQKE6/L/DhU50v4breh5+F8w1095/FHncBkPqtkYnvBgU1P2PeYdSo27esA+qbAvcIhQh1cNMJJGDHot84NuR7f3y3gUB8lpIir5xOhDYDEIblpZ/IiSRXBCM0uhOlCaX2yo5tGQqh4yJRNS65w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org; spf=pass smtp.mailfrom=somainline.org; arc=none smtp.client-ip=5.144.164.163 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=somainline.org Received: from Marijn-Arch-PC.localdomain (94-211-6-86.cable.dynamic.v4.ziggo.nl [94.211.6.86]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by m-r1.th.seeweb.it (Postfix) with ESMTPSA id DBCC11F925; Sat, 5 Oct 2024 23:43:22 +0200 (CEST) From: Marijn Suijten To: linux-bluetooth@vger.kernel.org Cc: Marijn Suijten , Yu Liu , Bartosz Fabianowski , Pauli Virtanen , Luiz Augusto von Dentz , Marek Czerski Subject: [PATCH BlueZ v4 1/3] audio/avrcp: Guard SetAbsoluteVolume without target behind config value Date: Sat, 5 Oct 2024 23:43:06 +0200 Message-ID: <20241005214321.84250-2-marijn.suijten@somainline.org> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241005214321.84250-1-marijn.suijten@somainline.org> References: <20241005214321.84250-1-marijn.suijten@somainline.org> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Commit 179ccb936 ("avrcp: Set volume if volume changed event is registered") invented a workaround that allows SetAbsoluteVolume to be sent to a remote device that does _not_ implement the AVRCP TG profile, as long as it previously registered for the EVENT_VOLUME_CHANGED notification. This is strange as the TG role is required to be able to send commands to the peer, but the commit must have been applied to the tree for a reason. We discussed in [1] that workarounds for dubious peers and software stacks should be guarded behind a config entry in main.conf, so this starts out by introducing a new [AVRCP] category that will later be extended with other workarounds. It guards the changed functionality behind a `VolumeWithoutTarget = false` boolean to disallow this obscure check. [1]: https://lore.kernel.org/linux-bluetooth/20211025210206.bkt5wovzmkmt6teg@SoMainline.org/ --- profiles/audio/avrcp.c | 16 +++++++++++++--- src/btd.h | 5 +++++ src/main.c | 16 ++++++++++++++++ src/main.conf | 6 ++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index 1622734b3..553673b19 100644 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -48,6 +48,7 @@ #include "src/dbus-common.h" #include "src/shared/timeout.h" #include "src/shared/util.h" +#include "src/btd.h" #include "avctp.h" #include "avrcp.h" @@ -4670,9 +4671,18 @@ int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify) &volume); } - if (!session->controller && !avrcp_event_registered(session, - AVRCP_EVENT_VOLUME_CHANGED)) - return -ENOTSUP; + if (btd_opts.avrcp.volume_without_target) { + /* If there is no target profile (we didn't create a controller for it), + * allow the call to pass through if the remote controller registered + * for a volume changed event. + */ + if (!session->controller && !avrcp_event_registered(session, + AVRCP_EVENT_VOLUME_CHANGED)) + return -ENOTSUP; + } else { + if (!session->controller || session->controller->version < 0x0104) + return -ENOTSUP; + } memset(buf, 0, sizeof(buf)); diff --git a/src/btd.h b/src/btd.h index 383bd7c19..147b61f12 100644 --- a/src/btd.h +++ b/src/btd.h @@ -104,6 +104,10 @@ struct btd_avdtp_opts { uint8_t stream_mode; }; +struct btd_avrcp_opts { + bool volume_without_target; +}; + struct btd_advmon_opts { uint8_t rssi_sampling_period; }; @@ -145,6 +149,7 @@ struct btd_opts { enum mps_mode_t mps; struct btd_avdtp_opts avdtp; + struct btd_avrcp_opts avrcp; uint8_t key_size; diff --git a/src/main.c b/src/main.c index 69ae1b1e3..5bd3a035d 100644 --- a/src/main.c +++ b/src/main.c @@ -165,6 +165,11 @@ static const char *avdtp_options[] = { NULL }; +static const char *avrcp_options[] = { + "VolumeWithoutTarget", + NULL +}; + static const char *advmon_options[] = { "RSSISamplingPeriod", NULL @@ -181,6 +186,7 @@ static const struct group_table { { "GATT", gatt_options }, { "CSIS", csip_options }, { "AVDTP", avdtp_options }, + { "AVRCP", avrcp_options }, { "AdvMon", advmon_options }, { } }; @@ -1140,6 +1146,13 @@ static void parse_avdtp(GKeyFile *config) parse_avdtp_stream_mode(config); } +static void parse_avrcp(GKeyFile *config) +{ + parse_config_bool(config, "AVRCP", + "VolumeWithoutTarget", + &btd_opts.avrcp.volume_without_target); +} + static void parse_advmon(GKeyFile *config) { parse_config_u8(config, "AdvMon", "RSSISamplingPeriod", @@ -1163,6 +1176,7 @@ static void parse_config(GKeyFile *config) parse_gatt(config); parse_csis(config); parse_avdtp(config); + parse_avrcp(config); parse_advmon(config); } @@ -1205,6 +1219,8 @@ static void init_defaults(void) btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; + btd_opts.avrcp.volume_without_target = false; + btd_opts.advmon.rssi_sampling_period = 0xFF; btd_opts.csis.encrypt = true; } diff --git a/src/main.conf b/src/main.conf index 82040b3fa..5d206b9ec 100644 --- a/src/main.conf +++ b/src/main.conf @@ -305,6 +305,12 @@ # streaming: Use L2CAP Streaming Mode #StreamMode = basic +[AVRCP] +# Allow SetAbsoluteVolume calls to a peer device that does not advertise the +# AVRCP remote control target profile. If it does advertise this profile, the +# version is ignored. +#VolumeWithoutTarget = false + [Policy] # # The ReconnectUUIDs defines the set of remote services that should try From patchwork Sat Oct 5 21:43:07 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marijn Suijten X-Patchwork-Id: 13823593 Received: from relay03.th.seeweb.it (relay03.th.seeweb.it [5.144.164.164]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 278B7171099 for ; Sat, 5 Oct 2024 21:50:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=5.144.164.164 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; cv=none; b=N5FQO1O6AMqrGTdNay6qyPeDAMngDvM9OMnLwh6/Y2h/h86Vq99uR9J7Is5o6LevsCqdTDvYq0GokEOvhVtAcYIEnLrUiO7KHN0KqoFD4cySECV2JUGpdZ/2DFOoE0+wumUcMABZ0pwUURkoaiPCoSO7t1OSVY1PE12Vbg5xf/U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; c=relaxed/simple; bh=h/QhMW4Jhi5jBjGR9cm/mVx/GG+1WBBiv8+Vo2XyMNY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=I+0bFVujqP523YHNlh+/ihXHYg62/ua4CkEKlDCPrrUMAkBmNoHkTesc87vKvol3KECONZMeW/DZ6zTJ9E+t6LxV2EU1brrcbVptbGnEImxqLvnFevKB7MIsqT7eKM3AlmbV6TJo+rCOrM/zfR6fG1iYMzgp4vfEqpJuXNT2rys= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org; spf=pass smtp.mailfrom=somainline.org; arc=none smtp.client-ip=5.144.164.164 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=somainline.org Received: from Marijn-Arch-PC.localdomain (94-211-6-86.cable.dynamic.v4.ziggo.nl [94.211.6.86]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by m-r1.th.seeweb.it (Postfix) with ESMTPSA id 54D161F927; Sat, 5 Oct 2024 23:43:23 +0200 (CEST) From: Marijn Suijten To: linux-bluetooth@vger.kernel.org Cc: Marijn Suijten , Yu Liu , Bartosz Fabianowski , Pauli Virtanen , Luiz Augusto von Dentz , Marek Czerski Subject: [PATCH BlueZ v4 2/3] audio/avrcp: Only allow absolute volume call/event on category-2 peers Date: Sat, 5 Oct 2024 23:43:07 +0200 Message-ID: <20241005214321.84250-3-marijn.suijten@somainline.org> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241005214321.84250-1-marijn.suijten@somainline.org> References: <20241005214321.84250-1-marijn.suijten@somainline.org> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Restrict the use of SetAbsoluteVolume and EVENT_VOLUME_CHANGED to peers with at least AVRCP version 1.4 and AVRCP_FEATURE_CATEGORY_2 on their respective target or controller profiles. For backwards-compatibility, add a (default-enabled) `VolumeCategory = true` configuration option under `[AVRCP]` to allow optionally disabling this new check. --- profiles/audio/avrcp.c | 39 ++++++++++++++++++++++++++++++++++----- src/btd.h | 1 + src/main.c | 5 +++++ src/main.conf | 5 +++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index 553673b19..005c3e306 100644 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -1775,6 +1775,16 @@ static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session, if (len != 1) goto err; + /** + * The controller on the remote end is only allowed to call SetAbsoluteVolume + * on our target if it's at least version 1.4 and a category-2 device. + */ + if (!session->target || session->target->version < 0x0104 || + (btd_opts.avrcp.volume_category && !(session->target->features & AVRCP_FEATURE_CATEGORY_2))) { + error("Remote SetAbsoluteVolume rejected from non-category-2 peer"); + goto err; + } + volume = pdu->params[0] & 0x7F; media_transport_update_device_volume(session->dev, volume); @@ -3767,6 +3777,16 @@ static void avrcp_volume_changed(struct avrcp *session, struct avrcp_player *player = target_get_player(session); int8_t volume; + /** + * The target on the remote end is only allowed to reply to EVENT_VOLUME_CHANGED + * on our controller if it's at least version 1.4 and a category-2 device. + */ + if (!session->controller || session->controller->version < 0x0104 || + (btd_opts.avrcp.volume_category && !(session->controller->features & AVRCP_FEATURE_CATEGORY_2))) { + error("Remote EVENT_VOLUME_CHANGED rejected from non-category-2 peer"); + return; + } + volume = pdu->params[1] & 0x7F; /* Always attempt to update the transport volume */ @@ -4041,7 +4061,7 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: case AVRCP_EVENT_UIDS_CHANGED: case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: - /* These events above requires a player */ + /* These events above require a player */ if (!session->controller || !session->controller->player) break; @@ -4245,10 +4265,13 @@ static void target_init(struct avrcp *session) if (target->version < 0x0104) return; + if (!btd_opts.avrcp.volume_category || target->features & AVRCP_FEATURE_CATEGORY_2) + session->supported_events |= + (1 << AVRCP_EVENT_VOLUME_CHANGED); + session->supported_events |= (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) | - (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED) | - (1 << AVRCP_EVENT_VOLUME_CHANGED); + (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED); /* Only check capabilities if controller is not supported */ if (session->controller == NULL) @@ -4665,8 +4688,11 @@ int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify) return -ENOTCONN; if (notify) { - if (!session->target) + if (!session->target || session->target->version < 0x0104 || + (btd_opts.avrcp.volume_category && !(session->target->features & AVRCP_FEATURE_CATEGORY_2))) { + error("Can't send EVENT_VOLUME_CHANGED to non-category-2 peer"); return -ENOTSUP; + } return avrcp_event(session, AVRCP_EVENT_VOLUME_CHANGED, &volume); } @@ -4680,8 +4706,11 @@ int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify) AVRCP_EVENT_VOLUME_CHANGED)) return -ENOTSUP; } else { - if (!session->controller || session->controller->version < 0x0104) + if (!session->controller || session->controller->version < 0x0104 || + (btd_opts.avrcp.volume_category && !(session->controller->features & AVRCP_FEATURE_CATEGORY_2))) { + error("Can't send SetAbsoluteVolume to non-category-2 peer"); return -ENOTSUP; + } } memset(buf, 0, sizeof(buf)); diff --git a/src/btd.h b/src/btd.h index 147b61f12..07205aa69 100644 --- a/src/btd.h +++ b/src/btd.h @@ -106,6 +106,7 @@ struct btd_avdtp_opts { struct btd_avrcp_opts { bool volume_without_target; + bool volume_category; }; struct btd_advmon_opts { diff --git a/src/main.c b/src/main.c index 5bd3a035d..89ee6897c 100644 --- a/src/main.c +++ b/src/main.c @@ -167,6 +167,7 @@ static const char *avdtp_options[] = { static const char *avrcp_options[] = { "VolumeWithoutTarget", + "VolumeCategory", NULL }; @@ -1151,6 +1152,9 @@ static void parse_avrcp(GKeyFile *config) parse_config_bool(config, "AVRCP", "VolumeWithoutTarget", &btd_opts.avrcp.volume_without_target); + parse_config_bool(config, "AVRCP", + "VolumeCategory", + &btd_opts.avrcp.volume_category); } static void parse_advmon(GKeyFile *config) @@ -1220,6 +1224,7 @@ static void init_defaults(void) btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; btd_opts.avrcp.volume_without_target = false; + btd_opts.avrcp.volume_category = true; btd_opts.advmon.rssi_sampling_period = 0xFF; btd_opts.csis.encrypt = true; diff --git a/src/main.conf b/src/main.conf index 5d206b9ec..fff13ed2f 100644 --- a/src/main.conf +++ b/src/main.conf @@ -311,6 +311,11 @@ # version is ignored. #VolumeWithoutTarget = false +# Validate that remote AVRCP profiles advertise the category-2 bit before +# allowing SetAbsoluteVolume calls or registering for EVENT_VOLUME_CHANGED +# notifications. +#VolumeCategory = true + [Policy] # # The ReconnectUUIDs defines the set of remote services that should try From patchwork Sat Oct 5 21:43:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marijn Suijten X-Patchwork-Id: 13823594 Received: from relay02.th.seeweb.it (relay02.th.seeweb.it [5.144.164.163]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 21B7517108A for ; Sat, 5 Oct 2024 21:50:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=5.144.164.163 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; cv=none; b=iZoVZsOP+wWH0SbWt/laiKz9TxrcRietPQH6OBYi6cOBCntKP7fTEZkh4nScMcGG9J00C/CPVlduaPmNZPweeeUbzaTzxKazXTYD2dg6dnUE/ZC1RAErvh43NVwXtug0NEwsbe/16OQNQ5FNDocsmNL3ReOVDRXhjbAObkO6wi0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728165010; c=relaxed/simple; bh=8RAxEDBmCwfMfXTHgbe16yv22rJr0MSYHi0ycZdApns=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sIth1nK84clRVWo2Wk/dcC6EnTCQJZRSPrj4Bjle+Z4+UR9ZDThTXRRf1Y8qelCcRkxdNRTSkbKb/AWujIc4dAf+zMtrlVfd5uJABprFJrSL7lZqv9ckBKrm6tYB+iDM8RFA0A94FOhNxy/Bu8TbNx7myyRjFc0DjbuezLE3klg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org; spf=pass smtp.mailfrom=somainline.org; arc=none smtp.client-ip=5.144.164.163 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=somainline.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=somainline.org Received: from Marijn-Arch-PC.localdomain (94-211-6-86.cable.dynamic.v4.ziggo.nl [94.211.6.86]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by m-r1.th.seeweb.it (Postfix) with ESMTPSA id C13A91F92B; Sat, 5 Oct 2024 23:43:23 +0200 (CEST) From: Marijn Suijten To: linux-bluetooth@vger.kernel.org Cc: Marijn Suijten , Yu Liu , Bartosz Fabianowski , Pauli Virtanen , Luiz Augusto von Dentz , Marek Czerski Subject: [PATCH BlueZ v4 3/3] audio/avrcp: Determine Absolute Volume support from feature category 2 Date: Sat, 5 Oct 2024 23:43:08 +0200 Message-ID: <20241005214321.84250-4-marijn.suijten@somainline.org> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241005214321.84250-1-marijn.suijten@somainline.org> References: <20241005214321.84250-1-marijn.suijten@somainline.org> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The AVRCP spec (1.6.2) does not mention anything about a version requirement for Absolute Volume, despite this feature only existing since spec version 1.4. Android reports a version of 1.3 [1] for its "AVRCP remote" (CT) service and mentions in the comment above it itself relies on feature bits rather than the exposed version. As it stands BlueZ requires at least version 1.4 making it unable to communicate absolute volume levels with even the most recent Android phones running Fluoride (have not checked the version on Gabeldorsche). The spec states that supporting SetAbsoluteVolume and EVENT_VOLUME_CHANGED are mandatory when feature level 2 is declared, excluded otherwise. This feature bit is set on Android and, when used by this patch, allows for successfully communicating volume back and forth despite the version theoretically being too low. In order to not affect spec tests too much (which I doubt would catch this, and should have otherwise pointed out that Android itself is out of spec) this behaviour is guarded behind a config option in main.conf, as discussed in [2]. Note that this workaround is deliberately omitted for the "AVRCP target" profile version, since Android already signals that to be 1.4 (which allows receiving SetAbsoluteVolume calls or registration for EVENT_VOLUME_CHANGED notifications) for other reasons [3]. [1]: https://android.googlesource.com/platform/system/bt/+/android-11.0.0_r28/bta/av/bta_av_main.cc#761 [2]: https://marc.info/?l=linux-bluetooth&m=163463497503113&w=2 [3]: https://android.googlesource.com/platform/system/bt/+/android-11.0.0_r28/bta/av/bta_av_main.cc#755 --- profiles/audio/avrcp.c | 20 ++++++++++++-------- src/btd.h | 1 + src/main.c | 5 +++++ src/main.conf | 9 +++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index 005c3e306..84172a6f6 100644 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -1779,9 +1779,10 @@ static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session, * The controller on the remote end is only allowed to call SetAbsoluteVolume * on our target if it's at least version 1.4 and a category-2 device. */ - if (!session->target || session->target->version < 0x0104 || + if (!session->target || + (btd_opts.avrcp.volume_version && session->target->version < 0x0104) || (btd_opts.avrcp.volume_category && !(session->target->features & AVRCP_FEATURE_CATEGORY_2))) { - error("Remote SetAbsoluteVolume rejected from non-category-2 peer"); + error("Remote SetAbsoluteVolume rejected from non-category-2 or non-AVRCP-1.4 peer"); goto err; } @@ -4262,13 +4263,15 @@ static void target_init(struct avrcp *session) (1 << AVRCP_EVENT_TRACK_REACHED_END) | (1 << AVRCP_EVENT_SETTINGS_CHANGED); - if (target->version < 0x0104) - return; - - if (!btd_opts.avrcp.volume_category || target->features & AVRCP_FEATURE_CATEGORY_2) + /* Remote device supports receiving volume notifications */ + if ((!btd_opts.avrcp.volume_version || target->version >= 0x0104) && + (!btd_opts.avrcp.volume_category || target->features & AVRCP_FEATURE_CATEGORY_2)) session->supported_events |= (1 << AVRCP_EVENT_VOLUME_CHANGED); + if (target->version < 0x0104) + return; + session->supported_events |= (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) | (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED); @@ -4688,9 +4691,10 @@ int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify) return -ENOTCONN; if (notify) { - if (!session->target || session->target->version < 0x0104 || + if (!session->target || + (btd_opts.avrcp.volume_version && session->target->version < 0x0104) || (btd_opts.avrcp.volume_category && !(session->target->features & AVRCP_FEATURE_CATEGORY_2))) { - error("Can't send EVENT_VOLUME_CHANGED to non-category-2 peer"); + error("Can't send EVENT_VOLUME_CHANGED to non-category-2 or non-AVRCP-1.4 peer"); return -ENOTSUP; } return avrcp_event(session, AVRCP_EVENT_VOLUME_CHANGED, diff --git a/src/btd.h b/src/btd.h index 07205aa69..61e4d309d 100644 --- a/src/btd.h +++ b/src/btd.h @@ -107,6 +107,7 @@ struct btd_avdtp_opts { struct btd_avrcp_opts { bool volume_without_target; bool volume_category; + bool volume_version; }; struct btd_advmon_opts { diff --git a/src/main.c b/src/main.c index 89ee6897c..e8504cbe3 100644 --- a/src/main.c +++ b/src/main.c @@ -168,6 +168,7 @@ static const char *avdtp_options[] = { static const char *avrcp_options[] = { "VolumeWithoutTarget", "VolumeCategory", + "VolumeVersion", NULL }; @@ -1155,6 +1156,9 @@ static void parse_avrcp(GKeyFile *config) parse_config_bool(config, "AVRCP", "VolumeCategory", &btd_opts.avrcp.volume_category); + parse_config_bool(config, "AVRCP", + "VolumeVersion", + &btd_opts.avrcp.volume_version); } static void parse_advmon(GKeyFile *config) @@ -1225,6 +1229,7 @@ static void init_defaults(void) btd_opts.avrcp.volume_without_target = false; btd_opts.avrcp.volume_category = true; + btd_opts.avrcp.volume_version = false; btd_opts.advmon.rssi_sampling_period = 0xFF; btd_opts.csis.encrypt = true; diff --git a/src/main.conf b/src/main.conf index fff13ed2f..b6b32a720 100644 --- a/src/main.conf +++ b/src/main.conf @@ -316,6 +316,15 @@ # notifications. #VolumeCategory = true +# Require peer AVRCP controllers to have at least version 1.4 before +# accessing category-2 (absolute volume) features (depending on the value +# of VolumeCategory above). It is common for Android-powered devices to not +# signal the desired minimum version of 1.4 while still supporting absolute +# volume based on the feature category bit, as mentioned in this comment: +# https://android.googlesource.com/platform/system/bt/+/android-12.0.0_r1/bta/ +# av/bta_av_main.cc#621 +#VolumeVersion = false + [Policy] # # The ReconnectUUIDs defines the set of remote services that should try