Added support for more music information with backwards compatibility

This commit is contained in:
TaaviE 2020-10-11 14:23:13 +03:00
parent 0b7d37c7eb
commit d4f383f885
3 changed files with 161 additions and 99 deletions

View File

@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import java.util.Objects;
public class MusicSpec {
public static final int MUSIC_UNKNOWN = -1;
public static final int MUSIC_UNDEFINED = 0;
public static final int MUSIC_PLAY = 1;
public static final int MUSIC_PAUSE = 2;
@ -27,12 +29,12 @@ public class MusicSpec {
public static final int MUSIC_NEXT = 4;
public static final int MUSIC_PREVIOUS = 5;
public String artist;
public String album;
public String track;
public int duration;
public int trackCount;
public int trackNr;
public String artist = null;
public String album = null;
public String track = null;
public int duration = MUSIC_UNKNOWN;
public int trackCount = MUSIC_UNKNOWN;
public int trackNr = MUSIC_UNKNOWN;
public MusicSpec() {

View File

@ -17,20 +17,26 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
/**
* Created by steffen on 07.06.16.
*/
public class MusicStateSpec {
public static final int STATE_PLAYING = 0;
public static final int STATE_PAUSED = 1;
public static final int STATE_STOPPED = 2;
public static final int STATE_UNKNOWN = 3;
public static final int STATE_UNKNOWN = -1;
public byte state;
public int position; // Position of the current media in seconds
public int playRate; // Speed of playback, usually 0 or 100 (full speed)
public byte shuffle;
public byte repeat;
public static final int STATE_PLAYING = 0;
public static final int STATE_PAUSED = 1;
public static final int STATE_STOPPED = 2;
public static final int STATE_SHUFFLE_ENABLED = 1;
public byte state = STATE_UNKNOWN;
/**
* Position of the current media in seconds
*/
public int position = STATE_UNKNOWN;
/**
* Speed of playback, usually 0 or 100 (full speed)
*/
public int playRate = STATE_UNKNOWN;
public byte shuffle = STATE_UNKNOWN;
public byte repeat = STATE_UNKNOWN;
public MusicStateSpec() {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Andreas Shimokawa
/* Copyright (C) 2020 Andreas Shimokawa, Taavi Eomäe
This file is part of Gadgetbridge.
@ -24,13 +24,14 @@ import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -59,46 +60,14 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private final DeviceInfoProfile<PineTimeJFSupport> deviceInfoProfile;
private static final UUID UUID_SERVICE_MUSICCONTROL = UUID.fromString("c7e50001-00fc-48fe-8e23-433b3a1942d0");
private static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("c7e50002-00fc-48fe-8e23-433b3a1942d0");
private static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0");
private static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0");
private static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0");
private static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0");
public PineTimeJFSupport() {
super(LOG);
addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION);
addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
addSupportedService(UUID_SERVICE_MUSICCONTROL);
deviceInfoProfile = new DeviceInfoProfile<>(this);
IntentListener mListener = new IntentListener() {
@Override
public void notify(Intent intent) {
String action = intent.getAction();
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}
};
deviceInfoProfile.addListener(mListener);
AlertNotificationProfile<PineTimeJFSupport> alertNotificationProfile = new AlertNotificationProfile<>(this);
addSupportedProfile(alertNotificationProfile);
addSupportedProfile(deviceInfoProfile);
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
requestDeviceInfo(builder);
onSetTime();
builder.notify(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_EVENT), true);
setInitialized(builder);
return builder;
}
/**
* These are used to keep track when long strings haven't changed,
* thus avoiding unnecessary transfers that are (potentially) very slow.
* <p>
* Makes the device's UI more responsive.
*/
String lastAlbum;
String lastTrack;
private void setInitialized(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
@ -163,7 +132,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
@ -253,19 +221,73 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
}
String lastArtist;
public PineTimeJFSupport() {
super(LOG);
addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION);
addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
deviceInfoProfile = new DeviceInfoProfile<>(this);
IntentListener mListener = new IntentListener() {
@Override
public void notify(Intent intent) {
String action = intent.getAction();
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}
};
deviceInfoProfile.addListener(mListener);
AlertNotificationProfile<PineTimeJFSupport> alertNotificationProfile = new AlertNotificationProfile<>(this);
addSupportedProfile(alertNotificationProfile);
addSupportedProfile(deviceInfoProfile);
}
/**
* Helper function that ust converts an integer into a byte array
*/
private static byte[] intToBytes(int source) {
return ByteBuffer.allocate(4).putInt(source).array();
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
requestDeviceInfo(builder);
onSetTime();
builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT), true);
setInitialized(builder);
return builder;
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
try {
TransactionBuilder builder = performInitialized("send playback info");
if (musicSpec.album != null) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_TRACK), musicSpec.track.getBytes());
if (musicSpec.album != null && !musicSpec.album.equals(lastAlbum)) {
lastAlbum = musicSpec.album;
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ALBUM, musicSpec.album.getBytes());
}
if (musicSpec.artist != null) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ARTIST), musicSpec.artist.getBytes());
if (musicSpec.track != null && !musicSpec.track.equals(lastTrack)) {
lastTrack = musicSpec.track;
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK, musicSpec.track.getBytes());
}
if (musicSpec.album != null) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ALBUM), musicSpec.album.getBytes());
if (musicSpec.artist != null && !musicSpec.artist.equals(lastArtist)) {
lastArtist = musicSpec.artist;
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ARTIST, musicSpec.artist.getBytes());
}
if (musicSpec.duration != MusicSpec.MUSIC_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL, intToBytes(musicSpec.duration));
}
if (musicSpec.trackNr != MusicSpec.MUSIC_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER, intToBytes(musicSpec.trackNr));
}
if (musicSpec.trackCount != MusicSpec.MUSIC_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL, intToBytes(musicSpec.trackCount));
}
builder.queue(getQueue());
@ -279,11 +301,30 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
try {
TransactionBuilder builder = performInitialized("send playback state");
byte[] state = new byte[]{0};
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
state[0] = 1;
if (stateSpec.state != MusicStateSpec.STATE_UNKNOWN) {
byte[] state = new byte[1];
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
state[0] = 0x01;
}
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_STATUS, state);
}
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_STATUS), state);
if (stateSpec.playRate != MusicStateSpec.STATE_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED, intToBytes(stateSpec.playRate));
}
if (stateSpec.position != MusicStateSpec.STATE_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_POSITION, intToBytes(stateSpec.position));
}
if (stateSpec.repeat != MusicStateSpec.STATE_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_REPEAT, intToBytes(stateSpec.repeat));
}
if (stateSpec.shuffle != MusicStateSpec.STATE_UNKNOWN) {
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_SHUFFLE, intToBytes(stateSpec.repeat));
}
builder.queue(getQueue());
} catch (Exception e) {
@ -292,6 +333,33 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
}
@Override
public boolean onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (super.onCharacteristicRead(gatt, characteristic, status)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
LOG.info("Unhandled characteristic read: " + characteristicUUID);
return false;
}
@Override
public void onSendConfiguration(String config) {
}
@Override
public void onReadConfiguration(String config) {
}
@Override
public void onTestNewFunction() {
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@ -300,7 +368,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
}
UUID characteristicUUID = characteristic.getUuid();
if (characteristicUUID.equals(UUID_CHARACTERISTICS_MUSIC_EVENT)) {
if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT)) {
byte[] value = characteristic.getValue();
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
@ -333,35 +401,21 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
return false;
}
@Override
public boolean onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (super.onCharacteristicRead(gatt, characteristic, status)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
LOG.info("Unhandled characteristic read: " + characteristicUUID);
return false;
}
@Override
public void onSendConfiguration(String config) {
}
@Override
public void onReadConfiguration(String config) {
}
@Override
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
/**
* This will check if the characteristic exists and can be written
* <p>
* Keeps backwards compatibility with firmware that can't take all the information
*/
private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) {
BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
if (characteristic != null &&
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
builder.write(characteristic, data);
}
}
}