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; import java.util.Objects;
public class MusicSpec { public class MusicSpec {
public static final int MUSIC_UNKNOWN = -1;
public static final int MUSIC_UNDEFINED = 0; public static final int MUSIC_UNDEFINED = 0;
public static final int MUSIC_PLAY = 1; public static final int MUSIC_PLAY = 1;
public static final int MUSIC_PAUSE = 2; 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_NEXT = 4;
public static final int MUSIC_PREVIOUS = 5; public static final int MUSIC_PREVIOUS = 5;
public String artist; public String artist = null;
public String album; public String album = null;
public String track; public String track = null;
public int duration; public int duration = MUSIC_UNKNOWN;
public int trackCount; public int trackCount = MUSIC_UNKNOWN;
public int trackNr; public int trackNr = MUSIC_UNKNOWN;
public MusicSpec() { public MusicSpec() {

View File

@@ -17,20 +17,26 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
/**
* Created by steffen on 07.06.16.
*/
public class MusicStateSpec { public class MusicStateSpec {
public static final int STATE_UNKNOWN = -1;
public static final int STATE_PLAYING = 0; public static final int STATE_PLAYING = 0;
public static final int STATE_PAUSED = 1; public static final int STATE_PAUSED = 1;
public static final int STATE_STOPPED = 2; public static final int STATE_STOPPED = 2;
public static final int STATE_UNKNOWN = 3;
public byte state; public static final int STATE_SHUFFLE_ENABLED = 1;
public int position; // Position of the current media in seconds
public int playRate; // Speed of playback, usually 0 or 100 (full speed) public byte state = STATE_UNKNOWN;
public byte shuffle; /**
public byte repeat; * 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() { 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. This file is part of Gadgetbridge.
@@ -24,13 +24,14 @@ import android.net.Uri;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; 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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@@ -59,46 +60,14 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private final DeviceInfoProfile<PineTimeJFSupport> deviceInfoProfile; 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"); * These are used to keep track when long strings haven't changed,
private static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0"); * thus avoiding unnecessary transfers that are (potentially) very slow.
private static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0"); * <p>
private static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0"); * Makes the device's UI more responsive.
private static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0"); */
String lastAlbum;
public PineTimeJFSupport() { String lastTrack;
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;
}
private void setInitialized(TransactionBuilder builder) { private void setInitialized(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
@@ -163,7 +132,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
} }
@Override @Override
public void onEnableRealtimeSteps(boolean enable) { 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 @Override
public void onSetMusicInfo(MusicSpec musicSpec) { public void onSetMusicInfo(MusicSpec musicSpec) {
try { try {
TransactionBuilder builder = performInitialized("send playback info"); TransactionBuilder builder = performInitialized("send playback info");
if (musicSpec.album != null) { if (musicSpec.album != null && !musicSpec.album.equals(lastAlbum)) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_TRACK), musicSpec.track.getBytes()); lastAlbum = musicSpec.album;
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ALBUM, musicSpec.album.getBytes());
} }
if (musicSpec.artist != null) { if (musicSpec.track != null && !musicSpec.track.equals(lastTrack)) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ARTIST), musicSpec.artist.getBytes()); lastTrack = musicSpec.track;
safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK, musicSpec.track.getBytes());
} }
if (musicSpec.album != null) { if (musicSpec.artist != null && !musicSpec.artist.equals(lastArtist)) {
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ALBUM), musicSpec.album.getBytes()); 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()); builder.queue(getQueue());
@@ -279,11 +301,30 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
try { try {
TransactionBuilder builder = performInitialized("send playback state"); TransactionBuilder builder = performInitialized("send playback state");
byte[] state = new byte[]{0}; if (stateSpec.state != MusicStateSpec.STATE_UNKNOWN) {
byte[] state = new byte[1];
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) { if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
state[0] = 1; state[0] = 0x01;
} }
builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_STATUS), state); safeWriteToCharacteristic(builder, PineTimeJFConstants.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()); builder.queue(getQueue());
} catch (Exception e) { } 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 @Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) { BluetoothGattCharacteristic characteristic) {
@@ -300,7 +368,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
} }
UUID characteristicUUID = characteristic.getUuid(); UUID characteristicUUID = characteristic.getUuid();
if (characteristicUUID.equals(UUID_CHARACTERISTICS_MUSIC_EVENT)) { if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT)) {
byte[] value = characteristic.getValue(); byte[] value = characteristic.getValue();
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl(); GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
@@ -333,35 +401,21 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport {
return false; 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 @Override
public void onSendWeather(WeatherSpec weatherSpec) { 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);
}
}
} }