/*
* 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