/* Copyright (C) 2024 Damien Gaignon, Martin.JM
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils.parseFormatBits;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
public class MusicControl {
public static final byte id = 0x25;
// TODO: should this be in HuaweiConstants?
public static final int successValue = 0x000186A0;
public static class MusicStatusRequest extends HuaweiPacket {
public MusicStatusRequest(ParamsProvider paramsProvider, byte commandId, int returnValue) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = commandId;
this.tlv = new HuaweiTLV()
.put(0x7F, returnValue);
this.isEncrypted = true;
this.complete = true;
}
}
public static class MusicStatusResponse extends HuaweiPacket {
public static final byte id = 0x01;
public int status = -1;
public MusicStatusResponse(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
}
@Override
public void parseTlv() throws ParseException {
if (this.tlv.contains(0x7F) && this.tlv.getBytes(0x7F).length == 4)
this.status = this.tlv.getInteger(0x7F);
}
}
public static class MusicInfo {
public static final byte id = 0x02;
public static class Request extends HuaweiPacket {
public Request(
ParamsProvider paramsProvider,
String artistName,
String songName,
byte playState,
byte maxVolume,
byte currentVolume
) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01, artistName)
.put(0x02, songName)
.put(0x03, playState)
.put(0x04, maxVolume)
.put(0x05, currentVolume);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public boolean ok = false;
public String error = "No input has been parsed yet";
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.isEncrypted = true;
}
@Override
public void parseTlv() throws ParseException {
if (this.tlv.contains(0x7F)) {
if (this.tlv.getInteger(0x7F) == successValue) {
this.ok = true;
this.error = "";
} else {
this.ok = false;
this.error = "Music information error code: " + Integer.toHexString(this.tlv.getInteger(0x7F));
}
} else {
this.ok = false;
this.error = "Music information response no status tag";
}
}
}
}
public static class Control {
public static final byte id = 0x03;
public static class Response extends HuaweiPacket {
public enum Button {
Unknown,
Play,
Pause,
Previous,
Next,
Volume_up,
Volume_down
}
public boolean buttonPresent = false;
public byte rawButton = 0x00;
public boolean volumePresent = false;
public byte volume = 0x00;
public Button button = null;
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.isEncrypted = false;
}
@Override
public void parseTlv() throws ParseException {
if (this.tlv.contains(0x01)) {
this.buttonPresent = true;
// Only grab the lowest byte
byte[] bytes = this.tlv.getBytes(0x01);
this.rawButton = bytes[bytes.length - 1];
switch (this.rawButton) {
case 1:
this.button = Button.Play;
break;
case 2:
this.button = Button.Pause;
break;
case 3:
this.button = Button.Previous;
break;
case 4:
this.button = Button.Next;
break;
case 5:
this.button = Button.Volume_up;
break;
case 6:
this.button = Button.Volume_down;
break;
case 64:
// Unknown button on Huawei Band 4
case 100:
// Seems like exit from music control screen to other screen
this.buttonPresent = false;
default:
this.button = Button.Unknown;
}
}
if (this.tlv.contains(0x02)) {
this.volumePresent = true;
// Only grab the lowest byte
byte[] bytes = this.tlv.getBytes(0x02);
this.volume = bytes[bytes.length - 1];
}
}
}
}
public static class UploadMusicFileInfo {
public static final int id = 0x09;
public static class UploadMusicFileInfoRequest extends HuaweiPacket {
public short songIndex;
public String songFileName;
public UploadMusicFileInfoRequest(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
this.songIndex = this.tlv.getShort(0x01);
this.songFileName = this.tlv.getString(0x02);
}
}
public static class UploadMusicFileInfoResponse extends HuaweiPacket {
public UploadMusicFileInfoResponse(ParamsProvider paramsProvider, short songIndex, String songName, String songArtist) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV().put(0x01, songIndex).put(0x03, songName).put(0x04, songArtist);
this.complete = true;
}
}
}
public static class MusicInfoParams {
public static final byte id = 0x04;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01)
.put(0x02)
.put(0x03);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
public int frameCount = 0;
public List pageStruct = null;
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
this.frameCount = this.tlv.getAsInteger(0x01);
if (this.tlv.contains(0x02))
params.availableSpace = this.tlv.getAsInteger(0x02);
if (this.tlv.contains(0x03)) {
byte[] formatBits = this.tlv.getBytes(0x03);
params.supportedFormats = parseFormatBits(formatBits);
}
if (this.tlv.contains(0x04))
params.maxMusicCount = this.tlv.getAsInteger(0x04);
if (this.tlv.contains(0x05))
params.currentMusicCount = this.tlv.getAsInteger(0x05);
if (this.tlv.contains(0x86)) {
this.pageStruct = new ArrayList<>();
List subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
for (HuaweiTLV subTlv : subTlvs) {
HuaweiMusicUtils.PageStruct pageStruct = new HuaweiMusicUtils.PageStruct();
if (subTlv.contains(0x08))
pageStruct.startFrame = subTlv.getShort(0x08);
if (subTlv.contains(0x09))
pageStruct.endFrame = subTlv.getShort(0x09);
if (subTlv.contains(0x0a))
pageStruct.count = subTlv.getShort(0x0a);
if (subTlv.contains(0x0b))
pageStruct.hashCode = subTlv.getBytes(0x0b);
this.pageStruct.add(pageStruct);
}
}
}
}
}
public static class MusicList {
public static final byte id = 0x05;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider, short startFrame, short endIndex) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01, startFrame)
.put(0x04, endIndex);
}
}
public static class Response extends HuaweiPacket {
public short startFrame = 0;
public short endIndex = 0;
public List musicList;
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws HuaweiPacket.ParseException {
if(tlv.contains(0x1))
startFrame = tlv.getShort(0x1);
if(tlv.contains(0x4))
endIndex = tlv.getShort(0x4);
musicList = new ArrayList<>();
if(this.tlv.contains(0x82)) {
for (HuaweiTLV subTlv : this.tlv.getObject(0x82).getObjects(0x83)) {
int index = subTlv.getAsInteger(0x4);
String title = subTlv.getString(0x5);
String artist = subTlv.getString(0x6);
String fileName = subTlv.getString(0x7);
musicList.add(new GBDeviceMusic(index, title, artist, fileName));
}
}
}
}
}
public static class MusicPlaylists {
public static final byte id = 0x06;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01);
}
}
public static class Response extends HuaweiPacket {
public static class PlaylistData {
public int id;
public String name;
public int frameCount;
}
public List playlists = new ArrayList<>();
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws HuaweiPacket.ParseException {
if(this.tlv.contains(0x81)) {
for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
PlaylistData data = new PlaylistData();
data.id = subTlv.getAsInteger(0x3);
data.name = subTlv.getString(0x4);
data.frameCount = subTlv.getAsInteger(0x5);
playlists.add(data);
}
}
}
}
}
public static class MusicPlaylistMusics {
public static final byte id = 0x07;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider, short playlist, short index) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01, playlist)
.put(0x02, index);
}
}
public static class Response extends HuaweiPacket {
public int id = -1;
public int index = -1;
public ArrayList musicIds = null;
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws HuaweiPacket.ParseException {
if(this.tlv.contains(0x1))
id = tlv.getAsInteger(0x1);
if(this.tlv.contains(0x2))
index = tlv.getAsInteger(0x2);
if(this.tlv.contains(0x3)) {
musicIds = new ArrayList<>();
ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x3));
while (dt.hasRemaining())
musicIds.add((int) dt.getShort());
}
}
}
}
public static class MusicOperation {
public static final byte id = 0x08;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider, int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01, (byte)operation);
if(operation == 1 || operation == 2 || operation == 3 || operation == 4) {
this.tlv.put(0x02, (short)playlistIndex);
}
if (operation == 0 || operation == 2) {
this.tlv.put(0x03, playlistName);
}
if (operation == 3 || operation == 4) {
ByteBuffer ids = ByteBuffer.allocate(musicIds.size() * 2);
for (Integer id : musicIds) {
ids.putShort(id.shortValue());
}
this.tlv.put(0x04, ids.array());
}
}
}
public static class Response extends HuaweiPacket {
public int operation = -1;
public int playlistIndex = -1;
public String playlistName;
public ArrayList musicIds = null;
public int resultCode = -1;
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws HuaweiPacket.ParseException {
if(this.tlv.contains(0x7f))
resultCode = tlv.getInteger(0x7f);
if(this.tlv.contains(0x1))
operation = tlv.getByte(0x1);
if(this.tlv.contains(0x2))
playlistIndex = tlv.getAsInteger(0x2);
if(this.tlv.contains(0x3))
playlistName = tlv.getString(0x3);
if(this.tlv.contains(0x4)) {
musicIds = new ArrayList<>();
ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x4));
while (dt.hasRemaining())
musicIds.add((int) dt.getShort());
}
}
}
}
public static class ExtendedMusicInfoParams {
public static final byte id = 0x0d;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01)
.put(0x02)
.put(0x03)
.put(0x04)
.put(0x05);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
if (this.tlv.contains(0x01))
params.availableSpace = this.tlv.getAsInteger(0x01);
if (this.tlv.contains(0x02)) {
byte[] formatBits = this.tlv.getBytes(0x02);
params.supportedFormats = parseFormatBits(formatBits);
}
if (this.tlv.contains(0x03))
params.maxMusicCount = this.tlv.getAsInteger(0x03);
if (this.tlv.contains(0x04))
params.maxPlaylistCount = this.tlv.getAsInteger(0x04);
if (this.tlv.contains(0x05))
params.unknown = this.tlv.getByte(0x05);
if (this.tlv.contains(0x86)) {
params.formatsRestrictions = new ArrayList<>();
List subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
for (HuaweiTLV subTlv : subTlvs) {
HuaweiMusicUtils.FormatRestrictions restriction = new HuaweiMusicUtils.FormatRestrictions();
if (subTlv.contains(0x08))
restriction.formatIdx = subTlv.getByte(0x08);
if (subTlv.contains(0x09)) {
String sampleRates = subTlv.getString(0x09);
restriction.sampleRates = sampleRates.split(",");
}
if (subTlv.contains(0x0a))
restriction.musicEncode = subTlv.getByte(0x0a);
if (subTlv.contains(0x0b))
restriction.bitrate = subTlv.getShort(0x0b);
if (subTlv.contains(0x0c))
restriction.channels = subTlv.getByte(0x0c);
if (subTlv.contains(0x0d))
restriction.unknownBitrate = subTlv.getShort(0x0b);
params.formatsRestrictions.add(restriction);
}
}
}
}
}
}