/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <set> #include <base/sys_byteorder.h> namespace bluetooth { namespace avrcp { constexpr uint32_t BLUETOOTH_COMPANY_ID = 0x001958; constexpr uint8_t MAX_TRANSACTION_LABEL = 0xF; enum class CType : uint8_t { CONTROL = 0x0, STATUS = 0x1, NOTIFY = 0x3, NOT_IMPLEMENTED = 0x8, ACCEPTED = 0x9, REJECTED = 0xa, STABLE = 0xc, CHANGED = 0xd, INTERIM = 0xf, }; enum class Opcode : uint8_t { VENDOR = 0x00, UNIT_INFO = 0x30, SUBUNIT_INFO = 0x31, PASS_THROUGH = 0x7c, }; // Found in AVRCP_v1.6.1 Section 4.5 Table 4.5 // Searching can be done in the spec by Camel Casing the constant name enum class CommandPdu : uint8_t { GET_CAPABILITIES = 0x10, LIST_APPLICATION_SETTING_ATTRIBUTES = 0x11, GET_ELEMENT_ATTRIBUTES = 0x20, GET_PLAY_STATUS = 0x30, REGISTER_NOTIFICATION = 0x31, SET_ABSOLUTE_VOLUME = 0x50, SET_ADDRESSED_PLAYER = 0x60, PLAY_ITEM = 0x74, }; enum class PacketType : uint8_t { SINGLE = 0x00, }; enum class Capability : uint8_t { COMPANY_ID = 0x02, EVENTS_SUPPORTED = 0x03, }; // Found in AVRCP_v1.6.1 Section 28 Appendix H enum class Event : uint8_t { PLAYBACK_STATUS_CHANGED = 0x01, TRACK_CHANGED = 0x02, PLAYBACK_POS_CHANGED = 0x05, PLAYER_APPLICATION_SETTING_CHANGED = 0x08, NOW_PLAYING_CONTENT_CHANGED = 0x09, AVAILABLE_PLAYERS_CHANGED = 0x0a, ADDRESSED_PLAYER_CHANGED = 0x0b, UIDS_CHANGED = 0x0c, VOLUME_CHANGED = 0x0d, }; enum class Attribute : uint32_t { TITLE = 0x01, ARTIST_NAME = 0x02, ALBUM_NAME = 0x03, TRACK_NUMBER = 0x04, TOTAL_NUMBER_OF_TRACKS = 0x05, GENRE = 0x06, PLAYING_TIME = 0x07, DEFAULT_COVER_ART = 0x08, }; enum class Status : uint8_t { INVALID_COMMAND = 0x00, INVALID_PARAMETER = 0x01, PARAMETER_CONTENT_ERROR = 0x02, INTERNAL_ERROR = 0x03, NO_ERROR = 0x04, UIDS_CHANGED = 0x05, RESERVED = 0x06, INVALID_DIRECTION = 0x07, NOT_A_DIRECTORY = 0x08, DOES_NOT_EXIST = 0x09, INVALID_SCOPE = 0x0a, RANGE_OUT_OF_BOUNDS = 0xb, FOLDER_ITEM_NOT_PLAYABLE = 0x0c, MEDIA_IN_USE = 0x0d, NOW_PLAYING_LIST_FULL = 0x0e, SEARCH_NOT_SUPPORTED = 0x0f, SEARCH_IN_PROGRESS = 0x10, INVALID_PLAYER_ID = 0x11, PLAYER_NOT_BROWSABLE = 0x12, PLAYER_NOT_ADDRESSED = 0x13, NO_VALID_SEARCH_RESULTS = 0x14, NO_AVAILABLE_PLAYERS = 0x15, ADDRESSED_PLAYER_CHANGED = 0x16, }; enum class BrowsePdu : uint8_t { SET_BROWSED_PLAYER = 0x70, GET_FOLDER_ITEMS = 0x71, CHANGE_PATH = 0x72, GET_ITEM_ATTRIBUTES = 0x73, GET_TOTAL_NUMBER_OF_ITEMS = 0x75, GENERAL_REJECT = 0xa0, }; enum class Scope : uint8_t { MEDIA_PLAYER_LIST = 0x00, VFS = 0x01, SEARCH = 0x02, NOW_PLAYING = 0x03, }; enum class Direction : uint8_t { UP = 0x00, DOWN = 0x01, }; enum class KeyState : uint8_t { PUSHED = 0x00, RELEASED = 0x01, }; class AttributeEntry { public: AttributeEntry(const Attribute& attribute, const std::string& value) : attribute_(attribute), value_(value) {} AttributeEntry(const Attribute& attribute) : attribute_(attribute) {} AttributeEntry(const AttributeEntry&) = default; Attribute attribute() const { return attribute_; } std::string value() const { return value_; } static constexpr size_t kHeaderSize() { size_t ret = 0; ret += 4; // Size of attribute field ret += 2; // Size of length field ret += 2; // Size of character encoding field return ret; } size_t size() const { return kHeaderSize() + value_.size(); } void resize(size_t new_size) { new_size = new_size < kHeaderSize() ? 0 : new_size - kHeaderSize(); if (value_.size() > new_size) { value_.resize(new_size); } } bool empty() { return value_.empty(); } bool operator<(const AttributeEntry& rhs) const { return attribute_ < rhs.attribute_; } private: Attribute attribute_; std::string value_; }; constexpr size_t MAX_FIELD_LEN = 100; struct MediaPlayerItem { uint16_t id_; std::string name_; bool browsable_; MediaPlayerItem(uint16_t id, const std::string& name, bool browsable) : id_(id), name_(name), browsable_(browsable) { if (name_.size() > MAX_FIELD_LEN) { name_.resize(MAX_FIELD_LEN); } } MediaPlayerItem(const MediaPlayerItem&) = default; static constexpr size_t kHeaderSize() { size_t ret = 0; ret += 1; // Media Player Type ret += 2; // Item Length ret += 2; // Player Id ret += 1; // Player Type ret += 4; // Player Subtype ret += 1; // Play Status ret += 16; // Features ret += 2; // UTF-8 character set ret += 2; // Name Length return ret; } size_t size() const { return kHeaderSize() + name_.size(); } }; struct FolderItem { uint64_t uid_; uint8_t folder_type_; bool is_playable_; std::string name_; FolderItem(uint64_t uid, uint8_t folder_type, bool is_playable, const std::string& name) : uid_(uid), folder_type_(folder_type), is_playable_(is_playable), name_(name) { if (name_.size() > MAX_FIELD_LEN) { name_.resize(MAX_FIELD_LEN); } } FolderItem(const FolderItem&) = default; static constexpr size_t kHeaderSize() { size_t ret = 0; ret += 1; // Folder Item Type ret += 2; // Item Length ret += 8; // Folder UID ret += 1; // Folder Type ret += 1; // Is Playable byte ret += 2; // UTF-8 Character Set ret += 2; // Name Length return ret; } size_t size() const { return kHeaderSize() + name_.size(); } }; // NOTE: We never use media type field because we only support audio types struct MediaElementItem { uint64_t uid_ = 0; std::string name_; std::set<AttributeEntry> attributes_; // Truncate the name and attribute fields so that we don't have a single item // that can exceed the Browsing MTU MediaElementItem(uint64_t uid, const std::string& name, std::set<AttributeEntry> attributes) : uid_(uid), name_(name) { if (name_.size() > MAX_FIELD_LEN) { name_.resize(MAX_FIELD_LEN); } for (AttributeEntry val : attributes) { val.resize(MAX_FIELD_LEN); attributes_.insert(val); } } MediaElementItem(const MediaElementItem&) = default; size_t size() const { size_t ret = 0; ret += 1; // Media Element Item Type ret += 2; // Item Length ret += 8; // Item UID ret += 1; // Media Type ret += 2; // UTF-8 Character Set ret += 2; // Name Length ret += name_.size(); ret += 1; // Number of Attributes for (const auto& entry : attributes_) { ret += entry.size(); } return ret; } }; struct MediaListItem { enum : uint8_t { PLAYER = 0x01, FOLDER = 0x02, SONG = 0x03 } type_; union { MediaPlayerItem player_; FolderItem folder_; MediaElementItem song_; }; MediaListItem(MediaPlayerItem item) : type_(PLAYER), player_(item) {} MediaListItem(FolderItem item) : type_(FOLDER), folder_(item) {} MediaListItem(MediaElementItem item) : type_(SONG), song_(item) {} MediaListItem(const MediaListItem& item) { type_ = item.type_; switch (item.type_) { case PLAYER: new (&player_) MediaPlayerItem(item.player_); return; case FOLDER: new (&folder_) FolderItem(item.folder_); return; case SONG: new (&song_) MediaElementItem(item.song_); return; } } ~MediaListItem() { switch (type_) { case PLAYER: player_.~MediaPlayerItem(); return; case FOLDER: folder_.~FolderItem(); return; case SONG: song_.~MediaElementItem(); return; } } size_t size() const { switch (type_) { case PLAYER: return player_.size(); case FOLDER: return folder_.size(); case SONG: return song_.size(); } } }; constexpr size_t AVCT_HDR_LEN = 3; } // namespace avrcp } // namespace bluetooth