Merge remote-tracking branch 'origin/master' into db-refactoring

master
cpfeiffer 2016-06-14 20:14:04 +02:00
commit a01507a924
30 changed files with 432 additions and 42 deletions

View File

@ -1,4 +1,12 @@
###Changelog
####Version 0.10.1
* Pebble: set extended music info by dissecting notifications on Android 5.0+
* Pebble: various other improvemnts to music playback
* Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble)
* Mi Band: support for shifting the device time by N hours (for people who sleep at daytime)
* Mi Band: initial and untested support for Mi Band 2
* Allow setting the application language
####Version 0.10.0
* Pebble: option to send sunrise and sunset events to timeline
* Pebble: fix problems with unknown app keys while configuring watchfaces

View File

@ -18,8 +18,8 @@ android {
targetSdkVersion 23
// note: always bump BOTH versionCode and versionName!
versionName "0.10.0"
versionCode 53
versionName "0.10.1"
versionCode 54
}
buildTypes {
release {

View File

@ -193,6 +193,9 @@ public class AppManagerActivity extends GBActivity {
if (!selectedApp.isConfigurable()) {
menu.removeItem(R.id.appmanager_app_configure);
}
if (mGBDevice != null && !mGBDevice.getFirmwareVersion().startsWith("v3")) {
menu.removeItem(R.id.appmanager_app_move_to_top);
}
menu.setHeaderTitle(selectedApp.getName());
}
@ -256,6 +259,9 @@ public class AppManagerActivity extends GBActivity {
startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
startActivity(startIntent);
return true;
case R.id.appmanager_app_move_to_top:
GBApplication.deviceService().onAppReorder(new UUID[]{selectedApp.getUUID()});
return true;
default:
return super.onContextItemSelected(item);
}

View File

@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -222,6 +223,15 @@ public class DebugActivity extends GBActivity {
musicSpec.trackNr = 2;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
MusicStateSpec stateSpec = new MusicStateSpec();
stateSpec.position = 0;
stateSpec.state = 0x01; // playing
stateSpec.playRate = 100;
stateSpec.repeat = 1;
stateSpec.shuffle = 1;
GBApplication.deviceService().onSetMusicState(stateSpec);
}
});

View File

@ -9,6 +9,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
/**
@ -25,6 +26,8 @@ public interface EventHandler {
void onSetCallState(CallSpec callSpec);
void onSetMusicState(MusicStateSpec stateSpec);
void onSetMusicInfo(MusicSpec musicSpec);
void onEnableRealtimeSteps(boolean enable);
@ -39,6 +42,8 @@ public interface EventHandler {
void onAppConfiguration(UUID appUuid, String config);
void onAppReorder(UUID uuids[]);
void onFetchActivityData();
void onReboot();

View File

@ -3,30 +3,54 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
public class MusicPlaybackReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
private static MusicSpec lastMusicSpec = new MusicSpec();
private static MusicStateSpec lastStatecSpec = new MusicStateSpec();
@Override
public void onReceive(Context context, Intent intent) {
String artist = intent.getStringExtra("artist");
String album = intent.getStringExtra("album");
String track = intent.getStringExtra("track");
LOG.info("Current track: " + artist + ", " + album + ", " + track);
/*
Bundle bundle = intent.getExtras();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
LOG.info(String.format("%s %s (%s)", key,
value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class"));
}
*/
MusicSpec musicSpec = new MusicSpec();
musicSpec.artist = artist;
musicSpec.album = album;
musicSpec.track = track;
musicSpec.artist = intent.getStringExtra("artist");
musicSpec.album = intent.getStringExtra("album");
musicSpec.track = intent.getStringExtra("track");
musicSpec.duration = intent.getIntExtra("duration", 0) / 1000;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
if (!lastMusicSpec.equals(musicSpec)) {
lastMusicSpec = musicSpec;
LOG.info("Update Music Info: " + musicSpec.artist + " / " + musicSpec.album + " / " + musicSpec.track);
GBApplication.deviceService().onSetMusicInfo(musicSpec);
} else {
LOG.info("got metadata changed intent, but nothing changed, ignoring.");
}
if (intent.hasExtra("position") && intent.hasExtra("playing")) {
MusicStateSpec stateSpec = new MusicStateSpec();
stateSpec.position = intent.getIntExtra("position", 0) / 1000;
stateSpec.state = (byte) (intent.getBooleanExtra("playing", true) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED);
if (!lastStatecSpec.equals(stateSpec)) {
LOG.info("Update Music State: state=" + stateSpec.state + ", position= " + stateSpec.position);
GBApplication.deviceService().onSetMusicState(stateSpec);
} else {
LOG.info("got state changed intent, but not enough has changed, ignoring.");
}
lastStatecSpec = stateSpec;
}
}
}

View File

@ -11,6 +11,11 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.service.notification.NotificationListenerService;
@ -25,6 +30,8 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
@ -161,13 +168,6 @@ public class NotificationListener extends NotificationListenerService {
return;
}
Prefs prefs = GBApplication.getPrefs();
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
if (powermanager.isScreenOn()) {
return;
}
}
switch (GBApplication.getGrantedInterruptionFilter()) {
case NotificationManager.INTERRUPTION_FILTER_ALL:
break;
@ -182,6 +182,17 @@ public class NotificationListener extends NotificationListenerService {
String source = sbn.getPackageName();
Notification notification = sbn.getNotification();
if (handleMediaSessionNotification(notification))
return;
Prefs prefs = GBApplication.getPrefs();
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
if (powermanager.isScreenOn()) {
return;
}
}
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
return;
}
@ -311,6 +322,79 @@ public class NotificationListener extends NotificationListenerService {
return false;
}
/**
* Try to handle media session notifications that tell info about the current play state.
*
* @param notification The notification to handle.
* @return true if notification was handled, false otherwise
*/
public boolean handleMediaSessionNotification(Notification notification) {
// this code requires Android 5.0 or newer
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
}
MusicSpec musicSpec = new MusicSpec();
MusicStateSpec stateSpec = new MusicStateSpec();
Bundle extras = notification.extras;
if (extras == null)
return false;
if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null)
return false;
MediaController c;
try {
c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION));
} catch (NullPointerException e) {
return false;
}
PlaybackState s = c.getPlaybackState();
stateSpec.position = (int) (s.getPosition() / 1000);
stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
stateSpec.repeat = 1;
stateSpec.shuffle = 1;
switch (s.getState()) {
case PlaybackState.STATE_PLAYING:
stateSpec.state = MusicStateSpec.STATE_PLAYING;
break;
case PlaybackState.STATE_STOPPED:
stateSpec.state = MusicStateSpec.STATE_STOPPED;
break;
case PlaybackState.STATE_PAUSED:
stateSpec.state = MusicStateSpec.STATE_PAUSED;
break;
default:
stateSpec.state = MusicStateSpec.STATE_UNKNOWN;
break;
}
MediaMetadata d = c.getMetadata();
if (d == null)
return false;
if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
musicSpec.trackCount = (int)d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
musicSpec.trackNr = (int)d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
// finally, tell the device about it
GBApplication.deviceService().onSetMusicInfo(musicSpec);
GBApplication.deviceService().onSetMusicState(stateSpec);
return true;
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {

View File

@ -14,9 +14,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
//import java.util.UUID;
public class GBDeviceService implements DeviceService {
protected final Context mContext;
protected final Class<? extends Service> mServiceClass;
@ -125,6 +128,17 @@ public class GBDeviceService implements DeviceService {
invokeService(intent);
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICSTATE)
.putExtra(EXTRA_MUSIC_REPEAT, stateSpec.repeat)
.putExtra(EXTRA_MUSIC_RATE, stateSpec.playRate)
.putExtra(EXTRA_MUSIC_STATE, stateSpec.state)
.putExtra(EXTRA_MUSIC_SHUFFLE, stateSpec.shuffle)
.putExtra(EXTRA_MUSIC_POSITION, stateSpec.position);
invokeService(intent);
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
@ -173,6 +187,13 @@ public class GBDeviceService implements DeviceService {
invokeService(intent);
}
@Override
public void onAppReorder(UUID[] uuids) {
Intent intent = createIntent().setAction(ACTION_APP_REORDER)
.putExtra(EXTRA_APP_UUID, uuids);
invokeService(intent);
}
@Override
public void onFetchActivityData() {
Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA);

View File

@ -18,12 +18,14 @@ public interface DeviceService extends EventHandler {
String ACTION_CALLSTATE = PREFIX + ".action.callstate";
String ACTION_SETTIME = PREFIX + ".action.settime";
String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo";
String ACTION_SETMUSICSTATE = PREFIX + ".action.setmusicstate";
String ACTION_REQUEST_DEVICEINFO = PREFIX + ".action.request_deviceinfo";
String ACTION_REQUEST_APPINFO = PREFIX + ".action.request_appinfo";
String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot";
String ACTION_STARTAPP = PREFIX + ".action.startapp";
String ACTION_DELETEAPP = PREFIX + ".action.deleteapp";
String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
String ACTION_APP_REORDER = PREFIX + ".action.app_reorder";
String ACTION_INSTALL = PREFIX + ".action.install";
String ACTION_REBOOT = PREFIX + ".action.reboot";
String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test";
@ -57,6 +59,11 @@ public interface DeviceService extends EventHandler {
String EXTRA_MUSIC_DURATION = "music_duration";
String EXTRA_MUSIC_TRACKNR = "music_tracknr";
String EXTRA_MUSIC_TRACKCOUNT = "music_trackcount";
String EXTRA_MUSIC_STATE = "music_state";
String EXTRA_MUSIC_SHUFFLE = "music_shuffle";
String EXTRA_MUSIC_REPEAT = "music_repeat";
String EXTRA_MUSIC_POSITION = "music_position";
String EXTRA_MUSIC_RATE = "music_rate";
String EXTRA_APP_UUID = "app_uuid";
String EXTRA_APP_START = "app_start";
String EXTRA_APP_CONFIG = "app_config";

View File

@ -1,5 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.model;
import java.util.Objects;
public class MusicSpec {
public static final int MUSIC_UNDEFINED = 0;
public static final int MUSIC_PLAY = 1;
@ -14,4 +16,23 @@ public class MusicSpec {
public int duration;
public int trackCount;
public int trackNr;
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MusicSpec)) {
return false;
}
MusicSpec musicSpec = (MusicSpec) obj;
return Objects.equals(this.artist, musicSpec.artist) &&
Objects.equals(this.album, musicSpec.album) &&
Objects.equals(this.track, musicSpec.track) &&
this.duration == musicSpec.duration &&
this.trackCount == musicSpec.trackCount &&
this.trackNr == musicSpec.trackNr;
}
}

View File

@ -0,0 +1,34 @@
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 byte state;
public int position;
public int playRate;
public byte shuffle;
public byte repeat;
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MusicStateSpec)) {
return false;
}
MusicStateSpec stateSpec = (MusicStateSpec) obj;
return this.state == stateSpec.state &&
Math.abs(this.position - stateSpec.position)<=2 &&
this.playRate == stateSpec.playRate &&
this.shuffle == stateSpec.shuffle &&
this.repeat == stateSpec.repeat;
}
}

View File

@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
@ -47,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_REORDER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
@ -65,6 +67,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
@ -87,6 +90,11 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIN
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_POSITION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_RATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_REPEAT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_SHUFFLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_STATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKCOUNT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKNR;
@ -351,6 +359,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
mDeviceSupport.onSetMusicInfo(musicSpec);
break;
case ACTION_SETMUSICSTATE:
MusicStateSpec stateSpec = new MusicStateSpec();
stateSpec.shuffle = intent.getByteExtra(EXTRA_MUSIC_SHUFFLE, (byte)0);
stateSpec.repeat = intent.getByteExtra(EXTRA_MUSIC_REPEAT, (byte)0);
stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0);
stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0);
stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte)0);
mDeviceSupport.onSetMusicState(stateSpec);
break;
case ACTION_REQUEST_APPINFO:
mDeviceSupport.onAppInfoReq();
break;
@ -372,6 +389,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
String config = intent.getStringExtra(EXTRA_APP_CONFIG);
mDeviceSupport.onAppConfiguration(uuid, config);
break;
}
case ACTION_APP_REORDER: {
UUID[] uuids = (UUID[]) intent.getSerializableExtra(EXTRA_APP_UUID);
mDeviceSupport.onAppReorder(uuids);
break;
}
case ACTION_INSTALL:
Uri uri = intent.getParcelableExtra(EXTRA_URI);

View File

@ -16,6 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
/**
@ -150,6 +151,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onSetCallState(callSpec);
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
if (checkBusy("set music state")) {
return;
}
delegate.onSetMusicState(stateSpec);
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
if (checkBusy("set music info")) {
@ -198,6 +207,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onAppConfiguration(uuid, config);
}
@Override
public void onAppReorder(UUID[] uuids) {
if (checkBusy("app reorder")) {
return;
}
delegate.onAppReorder(uuids);
}
@Override
public void onFetchActivityData() {
if (checkBusy("fetch activity data")) {

View File

@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
@ -599,6 +600,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
return telephoneRinging;
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
// not supported
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
// not supported
@ -761,6 +767,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
// not supported
}
@Override
public void onAppReorder(UUID[] uuids) {
// not supported
}
@Override
public void onScreenshotReq() {
// not supported

View File

@ -18,7 +18,6 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
@ -47,8 +46,7 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
@Override
public boolean isEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MISFIT);
return prefs.getBoolean("pebble_sync_misfit", true);
}
@Override

View File

@ -62,8 +62,7 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler {
@Override
public boolean isEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MORPHEUZ);
return prefs.getBoolean("pebble_sync_morpheuz", true);
}
@Override

View File

@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
abstract class DatalogSessionPebbleHealth extends DatalogSession {
@ -14,7 +13,6 @@ abstract class DatalogSessionPebbleHealth extends DatalogSession {
protected boolean isPebbleHealthEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_HEALTH);
return prefs.getBoolean("pebble_sync_health", true);
}
}

View File

@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
@ -70,6 +71,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final short ENDPOINT_RUNKEEPER = 7000;
static final short ENDPOINT_SCREENSHOT = 8000;
static final short ENDPOINT_NOTIFICATIONACTION = 11440; // 3.x only, TODO: find a better name
static final short ENDPOINT_APPREORDER = (short) 0xabcd; // 3.x only
static final short ENDPOINT_BLOBDB = (short) 45531; // 3.x only
static final short ENDPOINT_PUTBYTES = (short) 48879;
@ -1103,6 +1105,20 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) {
byte playState;
switch (state) {
case MusicStateSpec.STATE_PLAYING:
playState = MUSICCONTROL_STATE_PLAYING;
break;
case MusicStateSpec.STATE_PAUSED:
playState = MUSICCONTROL_STATE_PAUSED;
break;
default:
playState = MUSICCONTROL_STATE_UNKNOWN;
break;
}
int length = LENGTH_PREFIX + 12;
// Encode Prefix
ByteBuffer buf = ByteBuffer.allocate(length);
@ -1112,8 +1128,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(MUSICCONTROL_SETPLAYSTATE);
buf.put(state);
buf.putInt(position);
buf.put(playState);
buf.putInt(position * 1000);
buf.putInt(playRate);
buf.put(shuffle);
buf.put(repeat);
@ -1127,7 +1143,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (duration == 0) {
return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts);
} else {
byte[] stateMessage = encodeSetMusicState(MUSICCONTROL_STATE_PLAYING, 0, 100, (byte) 1, (byte) 1);
// Calculate length first
int length = LENGTH_PREFIX + 9;
if (parts != null) {
@ -1141,7 +1156,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
// Encode Prefix
ByteBuffer buf = ByteBuffer.allocate(length + stateMessage.length);
ByteBuffer buf = ByteBuffer.allocate(length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) (length - LENGTH_PREFIX));
buf.putShort(ENDPOINT_MUSICCONTROL);
@ -1165,8 +1180,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putShort((short) (trackCount & 0xffff));
buf.putShort((short) (trackNr & 0xffff));
buf.put(stateMessage);
return buf.array();
}
}
@ -1281,6 +1294,23 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeSimpleMessage(ENDPOINT_SCREENSHOT, SCREENSHOT_TAKE);
}
@Override
public byte[] encodeAppReorder(UUID[] uuids) {
int length = 2 + uuids.length * LENGTH_UUID;
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) length);
buf.putShort(ENDPOINT_APPREORDER);
buf.put((byte) 0x01);
buf.put((byte) uuids.length);
for (UUID uuid : uuids) {
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
}
return buf.array();
}
/* pebble specific install methods */
public byte[] encodeUploadStart(byte type, int app_id, int size, String filename) {
short length;
@ -1936,6 +1966,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
return sendBytes;
}
private GBDeviceEvent decodeAppReorder(ByteBuffer buf) {
byte status = buf.get();
if (status == 1) {
LOG.info("app reordering successful");
} else {
LOG.info("app reordering returned status " + status);
}
return null;
}
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData);
@ -2186,6 +2226,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
case ENDPOINT_BLOBDB:
devEvts = new GBDeviceEvent[]{decodeBlobDb(buf)};
break;
case ENDPOINT_APPREORDER:
devEvts = new GBDeviceEvent[]{decodeAppReorder(buf)};
default:
break;
}

View File

@ -15,6 +15,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
@ -108,6 +109,13 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
}
}
@Override
public void onSetMusicState(MusicStateSpec musicStateSpec) {
if (reconnect()) {
super.onSetMusicState(musicStateSpec);
}
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
if (reconnect()) {

View File

@ -11,6 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
@ -123,6 +124,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
sendToDevice(bytes);
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
byte[] bytes = gbDeviceProtocol.encodeSetMusicState(stateSpec.state, stateSpec.position, stateSpec.playRate, stateSpec.shuffle, stateSpec.repeat);
sendToDevice(bytes);
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr);
@ -147,6 +154,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
sendToDevice(bytes);
}
@Override
public void onAppReorder(UUID[] uuids) {
byte[] bytes = gbDeviceProtocol.encodeAppReorder(uuids);
sendToDevice(bytes);
}
@Override
public void onFetchActivityData() {
byte[] bytes = gbDeviceProtocol.encodeSynchronizeActivityData();

View File

@ -31,6 +31,10 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) {
return null;
}
public byte[] encodeFirmwareVersionReq() {
return null;
}
@ -51,6 +55,10 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeAppReorder(UUID[] uuids) {
return null;
}
public byte[] encodeSynchronizeActivityData() {
return null;
}

View File

@ -18,4 +18,8 @@
<item
android:id="@+id/appmanager_app_configure"
android:title="@string/app_configure"/>
<item
android:id="@+id/appmanager_app_move_to_top"
android:title="@string/app_move_to_top"/>
</menu>

View File

@ -238,5 +238,4 @@
<string name="updatefirmwareoperation_firmware_not_sent">Firmware non inviato</string>
<string name="charts_legend_heartrate">Battito cardiaco</string>
<string name="live_activity_heart_rate">Battito cardiaco</string>
<string name="miband_prefs_device_time_offset_hours">Offset orologio del dispositivo in ore (per l\'identificazione del sonno dei lavoratori a turni)</string>
</resources>

View File

@ -37,6 +37,7 @@
<string name="pref_title_theme">テーマ</string>
<string name="pref_theme_light">ライト</string>
<string name="pref_theme_dark">ダーク</string>
<string name="pref_title_language">言語</string>
<string name="pref_header_notifications">通知</string>
<string name="pref_title_notifications_repetitions">繰り返し</string>
<string name="pref_title_notifications_call">電話通知</string>
@ -57,7 +58,11 @@
<string name="pref_header_development">開発者用設定</string>
<string name="pref_title_development_miaddr">Mi Bandのアドレス</string>
<string name="pref_title_pebble_settings">Pebbleの設定</string>
<string name="pref_header_activitytrackers">アクティビティ トラッカー</string>
<string name="pref_title_pebble_activitytracker">お好みのアクティビティ トラッカー</string>
<string name="pref_title_pebble_sync_health">Pebble Health 同期</string>
<string name="pref_title_pebble_sync_misfit">Misfit 同期</string>
<string name="pref_title_pebble_sync_morpheuz">Morpheuz 同期</string>
<string name="pref_title_enable_pebblekit">第三者のアンドロイドアップにアクセス権利を与える</string>
<string name="pref_summary_enable_pebblekit">PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします</string>
<string name="pref_title_sunrise_sunset">日の出と日の入り</string>
@ -214,6 +219,7 @@
<string name="fwinstaller_firmware_not_compatible_to_device">このファームウェアは、デバイスと互換性がありません</string>
<string name="miband_prefs_reserve_alarm_calendar">今後のイベントのために予約するアラーム</string>
<string name="miband_prefs_hr_sleep_detection">睡眠の検出を改善するために心拍センサーを使用する</string>
<string name="miband_prefs_device_time_offset_hours">デバイスの時刻オフセット時間 (交代勤務の睡眠検知用)</string>
<string name="waiting_for_reconnect">再接続の待機中</string>
<string name="appmananger_app_reinstall">再インストール</string>
<string name="activity_prefs_about_you">あなたについて</string>

View File

@ -34,6 +34,7 @@
<string name="pref_title_theme">Тема</string>
<string name="pref_theme_light">Світла</string>
<string name="pref_theme_dark">Темна</string>
<string name="pref_title_language">Мова</string>
<string name="pref_header_notifications">Сповіщення</string>
<string name="pref_title_notifications_repetitions">Повтори</string>
<string name="pref_title_notifications_call">Виклики</string>
@ -203,4 +204,6 @@
<string name="appmanager_health_deactivate">Вимкнути</string>
<string name="app_configure">Конфігурація</string>
<string name="add_widget">Додати віджет</string>
<string name="device_hw">Пристрій: %1$s</string>
<string name="device_fw">ПЗ: %1$s</string>
</resources>

View File

@ -74,7 +74,13 @@
<string name="pref_title_development_miaddr">Mi Band address</string>
<string name="pref_title_pebble_settings">Pebble Settings</string>
<string name="pref_header_activitytrackers">Activity Trackers</string>
<string name="pref_title_pebble_activitytracker">Preferred Activitytracker</string>
<string name="pref_title_pebble_sync_health">Sync Pebble Health</string>
<string name="pref_title_pebble_sync_misfit">Sync Misfit</string>
<string name="pref_title_pebble_sync_morpheuz">Sync Morpheuz</string>
<string name="pref_title_enable_pebblekit">Allow 3rd Party Android App Access</string>
<string name="pref_summary_enable_pebblekit">Enable experimental support for Android Apps using PebbleKit</string>
@ -251,9 +257,11 @@
<string name="activity_prefs_weight_kg">Weight in kg</string>
<string name="appmanager_health_activate">Activate</string>
<string name="appmanager_health_deactivate">Deactivate</string>
<string name="app_configure">Configure</string>
<string name="app_move_to_top">Move to top</string>
<string name="authenticating">authenticating</string>
<string name="authentication_required">authentication required</string>
<string name="app_configure">Configure</string>
<string name="appwidget_text">Zzz</string>
<string name="add_widget">Add widget</string>

View File

@ -1,5 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.10.1" versioncode="54">
<change>Pebble: set extended music info by dissecting notifications on Android 5.0+</change>
<change>Pebble: various other improvemnts to music playback</change>
<change>Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble)</change>
<change>Mi Band: support for shifting the device time by N hours (for people who sleep at daytime)</change>
<change>Mi Band: initial and untested support for Mi Band 2</change>
<change>Allow setting the application language</change>
</release>
<release version="0.10.0" versioncode="53">
<change>Pebble: option to send sunrise and sunset events to timeline</change>
<change>Pebble: fix problems with unknown app keys while configuring watchfaces</change>

View File

@ -215,17 +215,31 @@
android:key="pebble_reconnect_attempts"
android:maxLength="4"
android:title="@string/pref_title_pebble_reconnect_attempts" />
<CheckBoxPreference
android:title="@string/pref_title_sunrise_sunset"
android:summary="@string/pref_summary_sunrise_sunset"
android:key="send_sunrise_sunset" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_header_activitytrackers">
<ListPreference
android:defaultValue="4"
android:entries="@array/pebble_activitytracker"
android:entryValues="@array/pebble_activitytracker_values"
android:key="pebble_activitytracker"
android:title="@string/pref_title_pebble_activitytracker"
android:summary="%s" />
android:summary="%s"
android:title="@string/pref_title_pebble_activitytracker" />
<CheckBoxPreference
android:title="@string/pref_title_sunrise_sunset"
android:summary="@string/pref_summary_sunrise_sunset"
android:key="send_sunrise_sunset" />
android:defaultValue="true"
android:key="pebble_sync_health"
android:title="@string/pref_title_pebble_sync_health" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pebble_sync_misfit"
android:title="@string/pref_title_pebble_sync_misfit" />>
<CheckBoxPreference
android:defaultValue="true"
android:key="pebble_sync_morpheuz"
android:title="@string/pref_title_pebble_sync_morpheuz" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_header_location">

View File

@ -12,6 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
public class TestDeviceSupport extends AbstractDeviceSupport {
@ -66,6 +67,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
@ -96,6 +102,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
public void onAppReorder(UUID[] uuids) {
}
@Override
public void onFetchActivityData() {

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.android.tools.build:gradle:2.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files